Useful Tech Issues

Contents

Nested IF Statements & Braces
Edge Testing
Data Type Conversion in Java
Color by the Numbers
Packed Numbers
  Imprecise Calculation
Fixed-Point Math
Faster Games

Related Documents

Learn Programming in Java -- Site Map for this whole tutorial
The Itty Bitty GameEngine -- Overview
Your Own Java Game -- Step by step tutorial to build a simple "Pong" game
Class GameWgt -- The visual components of a GameEngine game
Overriding GameEvent -- The programmatic components of a GameEngine game

Nested IF Statements & Braces

Let's make one thing clear: whether you put unnecessary (curly) braces or parentheses in your program, or leave them out, they have no effect on how well or fast your program runs. It may take the compiler an extra millisecond or two to process them, but all at compile time, they are all gone when your program runs.

So why worry about it at all? Just this: finding a missing or extra parenthesis or brace in a large program is one of the hardest things to do (without help from the compiler, which is not forthcoming in BlueJ). Most compilers won't even give you an accurate error message, just that somewhere, far, far away from the actual error, the compiler figures out that this is utterly wrong -- and says the first thing it can think of. Some development environments (IDEs) at least let you click on a parenthesis or brace, and it will show you what it thinks is its matching pair. BlueJ sometimes doesn't do that, but they draw colored boxes around the blocks enclosed by braces, so if the colored box goes past where it should, that's a clue.

Most good programmers indent the contents of a brace-enclosed block, which gives you a visual hint where the braces should start and end. The compiler could check that, but they don't. The C tradition is to put the starting and ending braces on lines by themselves, but I prefer to use that valuable screen real estate for actual code.

Anyway, braces are not required around a single statement that is the body of a loop or under the control of an IF or ELSE, but many teachers tell you to put them in anyway. The one place where you should put the unnecessary braces in is for nested IF statements, if there's an ELSE involved. Technically, the ELSE always belongs to the nearest unclosed IF, so if you have some code like this:

if (firstly)
  if (secondly)
DoSomething();
else DoMore();
and if firstly is false then nothing happens (nevermind that the indentation suggests that DoMore should have been called; DoMore only gets called when firstly is true and secondly is false. I always put braces around the outer body, so the compiler will do it the way I intended (or else complain).
 

Edge Testing

If your game involves moving things bumping into each other, GameEngine has a built-in sprite widget which can be given a velocity that will automatically move across the screen at a fixed speed, and you can be notified when that sprite bumps into any other widget that is marked as collidable (see SetFlags and Collided), or if it runs off the game board. The collision testing is done on the vertical and horizontal components of the position at the same time using the techniques described in "Packed Numbers" below. This all happens under the hood in the GameEngine, so you do not need to worry about it.

However, you might need to consider how it decided that there is a collision, in case you need to see if it will be detected again (on the next frame), or if you want to move things far enough apart to prevent that. Basically the top-left corner of one widget is compared to the bottom-right corner of the other widget for overlap. If there is separation, either bottom to top or right to left, or just touching but not overlapped, then there is no collision. Then the opposite two corners are similarly compared. The top-left corner of a widget is obtained by the GetPosn() call. The bottom-right corner is the sum of the top-left plus the widget's containing rectangle dimensions from GetSize(). Overlap is determined by subtracting (negate then add) the bottom-right from the top-left and seeing if either half is negative: if both halves are non-negative, there is no overlap. You can do this kind of testing yourself, if the GameEngine tests don't fit your requirements. Look at the Collider() method in the JavaGame class to see how I did it.
 

Data Type Conversion in Java

In a strongly typed language (Java tries hard to be strongly typed, but fails in a few significant ways), the compiler will always tell you when you try to do something inappropriate for the types of the data you are trying to do it to. Characters are letters, not numbers, so you should not be able to do arithmetic on them. The type name "void" should be a valid (zero bits) data type, not compatible with any other data type. If a subroutine return type is void, (like DoSomething in the above example), the compiler will not let you put the result into a boolean variable; but conversely, if a function returns a boolean, the compiler should not let you put the result nowhere (that is, discard it) by, for example, calling the subroutine on a line by itself without testing the result in a conditional, or assigning it to a boolean variable. Both C and Java made that mistake, and it leads to subtle errors that the compiler may or may not be able to catch.

Say you write a couple lines in Java like this:

boolean A = false, B = !A; // int A = 3, B = A+1;
if (A=B) System.print("Oops!");
what do you think will happen? Try it! Did you expect it to print "Oops!"? We normally put assignment statements on a line by themselves, they are not expected to deliver a value, but in Java (which got this bad habit from C) they do! You thought you were testing the value variable A against the value in variable B, when Java thought you were asking it to assign (copy) the value in B and put it (true) into A, and then because that is true, it went on to do the print. If you switch the first line around so that the variables are integers instead of booleans, the compiler will complain, but only because the result of the assignment is integer, but it needs a boolean for testing; C (and C++) merrily accepts it and the hasty reader will see the "=" inside an IF and happily think it's a comparison, nothing wrong there, and never figure out why their program fails. Compilers can do better. My compiler does better. But I didn't get rich selling my compiler. I didn't even get rich selling my book about compilers, but that's another story.

The point is, you should write code as if your compiler forces strong typing on you, because you will make fewer mistakes.

The topic here is data conversion. In Java, a char is just another number. Indeed it is, in the underlying hardware, but everything is just numbers. The whole point of a strongly typed high-level language is to make a difference between characters and numbers, so the compiler can help us catch mistakes in usage. So I have an explicit type conversion method for changing strings of characters into numbers. I also have another one for changing numbers into strings, but Java is inconsistent about allowing that, so I got lazy. Consider this program fragment:

String abc = "123", def;
int xyz = 123, uvw;
uvw = xyz + 4;
def = abc + 4;
Now you really should not think of concatenation as "adding" because it's fundamentally different. If the last two lines above are separated from the declarations, you might be tempted to think that whatever number is in variable abc just got 4 added to it, just like the previous line did to xyz. Now what really happened is that the character "4" got stuck in a memory location adjacent to the String value "123" so that it formed the value "1234".
System.print("The value of 2+3=" + 2+3);
Would you believe a printout "The value of 2+3=23"? Me neither. So here we have the automatic conversion of numbers to strings before applying an operator "+" to them which does not add the numbers even though that's what it looks like you intended. OK, let's do the same thing in a fifth line to the same program:
def = 4 + abc;
except now the compiler complains. You can't even say
def = 4;
Go figure. Always do your conversions explicitly in Java. The compiler won't always help you remember, but you might save yourself some grief. But, like I said, I got lazy in this program. Or rather, my compiler always converts any primitive type to String when asked to (because it is so useful, as the Java people well know ;-) so I still do that in print statements. Which is (mostly) compatible with standard Java.
 

Color by the Numbers

Java uses a 32-bit RGB color model, where the top 8 bits are deemed to be an alpha mask -- I don't remember whether all-zero or all-ones is deemed to be opaque, that's under the hood, and I got it right and it ain't broke, so it doesn't need fixing. All you and I need to be concerned with is how to specify the 24 bits of color that our games are responsible for. They are three 8-bit numbers, packed into one 32-bit integer read out (in hexadecimal) as 0x00rrggbb, that is, blue is the low 8 bits and red is the high 8 bits, and green is the middle byte. For each byte, all zero is black = none of this color, and all ones = 255 is white = all of this color.

Pure red is 0xFF0000, pure green is 0x00FF00, and pure blue is 0x0000FF. Add these up for different combinations. Some color picker tools let you choose or see the RGB numerical value for a color, so you could pick off those values when you want those colors. GameEngine's color chooser only lets you choose color by the number. Perhaps a future version might give you sliders or a color wheel, but there are more pressing improvements before that even gets considered. "Real programmers code in hex." I normally don't argue that way, but this is an exception.
 

Packed Numbers

The first computer I ever owned outright had 160 bytes of RAM (writeable storage) plus 4K (4096 bytes) of program memory and I think 128K of floppy disk file storage. I worked hard to pack bigger programs into tiny spaces. Old habits die hard, but my computer here today is already maxed out in storage space, so these are good habits. The real world you live in, bytes are free, computer cycles are free, you don't know what you are missing. The internet is much faster than when I got on, but in a couple years it will be far faster. So for a couple years, sending small data files is better than sending big ones. This is about smaller data. If you ever start programming IoT ("Internet of Things") or wearables or lunar lander software, space and time will be important again. Even if you never need to do it, it's still a good thing to know.

Oh did I mention? GameEngine does not use the graphics processor (GPU) on your computer, everything is pure Java, so you can read it like as if you wrote it yourself. But pure C or Java runs about ten times slower than the GPU, so we need to be careful (see "Faster Games" below).

Often the numbers we need to use in a small environment never get bigger than the size of the environment. GameEngine can have maybe a thousand widgets on screen before it gets too slow to play, and the screen on the largest computer you would ever play this game on is maybe 4K pixels wide, so we are talking numbers that never get bigger than a dozen bits. I program everything in 32 bits (it's slightly faster than 64 bits) but nobody makes 32-bit computers any more, they are all 64. Java has descriptors for different sizes of numbers, but you can't mix them in large arrays, you need to use classes and objects, and those take time, a lot more time than just packing two or more smaller numbers into 32 bits. The hardware has instructions for packing and unpacking powers of 2 chunks into 32- and 64-bit numbers (and any compiler worth its salt knows how to use that hardware), so the packing is almost free.

But not quite. So when I pack a vertical + horizontal  pair of coordinates into a single integer, I might want to compare the location of the ball to the corner of the screen without unpacking the numbers for a tiny increase in speed. Raster-based graphics were designed to be accessed in TV scan order, which is the same as (western) text order in books, doing the whole top line left to right, then advancing to the next line down, which is called "row major" because the whole row is stored in memory before advancing to the next row (see "Row Major Ordering"). Again, (western) number systems are "Big Endian" (the big end of the number is encountered first when scanned sequentially in normal reading), whereas math is normally computed "Little Endian" so the carries propagate correctly. People who grew up in Big Endian hardware do their row-major pixel array dimensions with the row (vertical coordinate) in the most-significant end of the integer, and the column in the lower half of the number. In Java this looks like:

int pack(int row, int col) {returm (row<<16)+col;}

int getrow(int coord) {return coord>>16;}
int getcol(int coord) {return coord&0xFFFF;}

The low half part only works for positive coordinates. When you have values that could be negative -- for example, the ball rolled off the top of the screen -- then you need to consider how negative numbers are stored (or else just use the built-in SignExtend function). All computers on the market since the invention of the microprocessor 50 years ago use "two's complement" negative integers, which is the simplest hardware for adding and subtracting. The one-bit "full adder" (which includes the carry in and carry out) is nine transistors, so a simple 32-bit adder used 288 transistors; subtracting inverts all the bits of one input, another 3 transistors, a total of 384. There are more transistors (gates) used to speed up carry propagation, but this is a programming class, not hardware. If you are interested in the low-level hardware, you can look at my GateSim program, which explains how everything works down to the transistor (gate) level; you can even build a working computer at this level!

The point is, the hardware choices were made 50 years ago when computer chips had thousands, not billions, of transistors on a single chip. So what do two's complement signed numbers look like? Let's count up from 0 to 15 in binary, then (reading the table backwards), down from -1 to -16 (which is the same as zero):
 

0 0000 -16
1 0001 -15
2 0010 -14
3 0011 -13
4 0100 -12
5 0101 -11
6 0110 -10
7 0111 -9
8 1000 -8
9 1001 -7
10 1010 -6
11 1011 -5
12 1100 -4
13 1101 -3
14 1110 -2
15 1111 -1

There are some interesting observations. If you ignore the signs on the right column, the numbers in that column plus the number in the left column of the same row always add up to 16. That's because this is a 4-bit table. If there were only one bit, then the numbers would add up to two, which is why it's called "two's complement".

Then you can see that the middle line, +8 is the same binary number as -8. That is true of any size number, the middle value is a one on the left end and otherwise all zeroes. We define that left bit to be the sign bit, so (in this 4-bit system) the only positive numbers are from 0 to 7; the others are all negatives, and you cannot have a more negative number than -8. Well, you can, but it's no longer recognizable as negative. When you count up past 8, it flips over to negative. If you stacked two copies of this table, one above the other, you'd see that -1 comes just before zero, which is as it should be.

Anyway, if you want to pick out the low half of a packed number, GameEngine has a utility that makes a 32-bit signed number from it. Some Java compilers are smart enough to back-substitute this into the calling code, so it's almost as fast as the unsigned version:

int SignExtend(int coord) {return (coord&0x7FFF)-(coord&0x8000);}


One more observation, to make the negative of any number, you complement (flip 1->0 and 0->1) all the bits, then add +1. So for example, the negative of +3 [0011] => [1100]+1 = [1101] = 13. It even works for zero: [0000] => [1111]+1 = [10000] = [0000] (after we discard the excess carry out).

Now let's try packing two (signed) 4-bit numbers into a single 8-bit integer. We try a few values to see what happens:

[+3,+6] becomes  [0011,0110] = [00110110] = 54
[+3,-6] becomes  [0011,1010] = [00111010] = 58
[-3,+6] becomes  [1101,0110] = [11010110] = 214 = -42
[-3,-6] becomes  [1101,1010] = [11011010] = 218 = -38
There are several interesting facts to learn from this exercise. First, the sign of the upper half is the sign of the whole. So to do a sign check on that upper half, you can just test the sign of the whole packed number. You can test the signs of both halves at once by masking out only the two sign bits, using two copies of the most negative number in the half-range (in our case -8) packed together [10001000]=136 then logical-AND that to any packed number, and if the result is zero, then both halves are non-negative; if it is 136 (the mask value), then both halves are negative.

To test only the lower half for negative just pick out the lower sign bit, which for 16-bit halves would be (num32&0x8000)==0 for positive or !=0 for negative. It's less intuitive than using SignExtend, but substantially faster (especially if your compiler optimizes badly).

The surprising fact here is that packing the negatives of two 4-bit numbers does not create the negative of the 8-bit number that the two positives formed, it is off by 16 (one unit in the least-significant bit of the left half). You can think of it as because a negative 8-bit number like -6 has ones in all four bits of its left half (-1) which must be accounted for, but that doesn't help much with understanding it. It just is what it is.

Anyway, so if you add a packed number to packed negatives of its parts, like [+3,+6]+[-3,-6] = 54+218 = 272 = +16, you don't get zero. For the math to work correctly you must add the two halves separately, then put them back together. For a compare (subtraction) to work correctly, accurately all the way across, you must subtract the two halves separately, then put them back together. You can safely do full-word adds and subtracts when you know that the lower-half result has the same sign as the original. lFor example, if you subtract a the ball size (known to be two small positive numbers) from the game board size (two large positive numbers), the result is slightly reduced but no sign change in a single 32-bit operation.

You have Java code for packing and unpacking these two numbers on a 16+16 basis (see "pack" above), which is simple enough to do in-line. GameEngine has a couple utility functions, AddPair and PairNeg, for adding the 16-bit halves of two numbers together then repacking the result so you get the correct sums, and for taking the negative of the two 16-bit halves of a number then repacking the result, which you can use to take the difference of two packed values. For equality testing, of course you can just compare the packed values: if both halves are equal, then the packed values will also be equal. Testing one for greater than the other doesn't make sense, because one half might be greater one way, while the other half might be greater the other way.

But sometimes a tiny error in the left half just isn't important, see "Imprecise Calculation" next.
 

Imprecise Calculation

Any time you try to model the real world in a digital computer, there will be imprecision, where our digital numbers do not exactly match the real world. You can see this in the Pong game, of the "Laying Out Your First GameEngine Game" example, where I suggested a "slight downward drift 0.1 pixels per frame." You cannot draw a fraction of a pixel. Most image scaling algorithms blur the fractional pixels across two or four actual pixels, but they use a high-speed GPU (graphics hardware now in most personal computers and smart phones) to do that. The result is that instead of the ball going steadily to the right for ten frames then popping down one pixel in the next frame (as the GameEngine does it) the edges get all blurry so you can't really tell where they are. Apple does this for their fonts, and the result is much harder to read. If you are running GameEngine on an OSX computer, you might notice that the fonts are crisp and readable, because I don't use the Apple font blurring mechanism (even the font rendering is in my own Java code).

Anyway the ball position is an approximation, rounded to the nearest pixel, sampled at 10 frames per second. It's all approximate. So now we want to reverse the direction of the ball on the screen. It's all an approximation, so 0.25% of a pixel more or less per frame will not even be visible. So I look at the logic for finding the negative of a number, which is to flip all the bits to their opposite, zeroes for ones and ones for zeroes, then add +1. If I leave out the +1, there's no carry out of the lower half into the upper half (crosstalk is worse than imprecision), so it's off by 0.24 %, big whoop-de-doo. Flipping any number of bits all at the same time is a single hardware instruction, that every computer except the original PDP-8 minicomputer does in one cycle, and Java has a bitwise operator "^" to call up this hardware instruction, so I can do that for the upper or lower half, whichever direction I want to reverse. If you wanted to do it exactly, you'd write something like this:

if (velo<0) velo = (velo&0xFFFF)-(velo&-0x10000); // upper half
if (...)    velo = (-velo)&0xFFFF|(velo&-0x10000); // lower half
which would be four or five operations at the hardware level instead of one (the difference is probably insignificant, like the difference in value ;-) or else
if (velo<0) velo = (velo^-0x10000)+0x10000; // upper half only
which inverts the top 16 bits as before, then adds +1 to those top 16 bits, which is two machine operations, but this does not work for the lower half, because the carry out of the +1 messes up the upper half. The point is, by trying different ways to do things, you can improve on its performance and size, sometimes at the risk of insignificant degradation in accuracy. But it's much simpler (and insignificantly slower) to get the half you want and do the math naturally, so that's the way Pong now works.

Then there are fractional numbers which almost never can be exact, as we can see in the next topic.
 

Fixed-Point Math

GameEngine does not use any floating-point numbers at all, everything is calculated in 32-bit integers. There are some places that do fractional calculations, like the animation rate for icons, and the velocity vector for sprites, but these are evaluated in fixed-point numbers, which are integers with an implied constant divisor, usually (as in GameEngine) a power of two.

The velocity vector of sprites is the easiest to understand. Each 16-bit half of the velocity integer is a fixed-point value with 8 bits of fraction and 8 bits of sign+integer part. Another pair of 8-bit values is maintained for each sprite, which represents the current fractional position (the GetPosn() method returns only the integer position of any widget on the screen). To this fraction is added the whole 16-bit velocity part for vertical or horizontal, then the integer part of the sum is shifted (right) 8 places and added to the regular (integer) position, and the remaining 8-bit fractional part is saved for the next frame. This isn't rocket science, but it does require some careful thought. Fortunately, GameEngine does it for you. It is inline code in the sprite widget, not a subroutine you can call for your own fractional math, so if you want to do the same kind of thing in your own game, you need to write your own code to do it. It's easier to get the fixed-point value converted to floating-point (double, all done in the method call) do the math, then convert it back (again inside the method where you don't see it).
 

Faster Games [TBD]

 

 
 
 
 
 

Rev. 2020 July 13