1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | //*************** //Initialize any array holding the //string hex representation of 0-255 //=>borrowed from the JavaScript Wolfenstein //*************** h="0123456789ABCDEF" N=[]; f=Math.floor; for(i=0;i<256;i++) N[i]="0x"+h.substr(f(i/16),1)+h.substr(i%16,1) //*************** //what size we want out screen to be screenW = 160; screenH = 120; //we need to change the frame number //every frame or the browser will not //update the image frame=0; //******************** //code to initialize the //screen memory //******************** var screen = []; for(y=0;y<screenH;y++) for(x=0;x<screenW/8;x++) screen[x+y*screenW/8]=0; //******************** //function to take the screen //and display it to the user //******************** function render() { //we have to convert the numerical values of the screen into //the hex string representation (example: 255 => 0xff) conv = []; for(y=0;y<240;y++) for(x=0;x<screenW/8;x++) conv[x+y*screenW/8] = N[255-screen[x+y*screenW/8]]; //send the result into the document image z="#define pic_"; im=z+"width "+screenW+"\n"+z+"height "+screenH+"\nstatic char pic_bits[] = {"+conv.join(",")+"}"; document.screen.src="JavaScript:"+frame+";im;"; } //******************** //our main loop //we handle updating //and rendering everything //from here //******************** function main() { frame++; //document.d.debug.value=frame + "\n" + pointX + ", " + pointY; draw(); render(); } //******************** //The ClearScreen function //used to zero out the screen //array //******************** function ClearScreen() { max=screenW/8 * screenH; for(x=0;x<max;x++) screen[x]=0; } //******************** //The PutPixel function //is used to place a pixel //on the screen. //x is the horizontal position //y is the vertical position //c is the color (0 or 1 in our case) //******************** function PutPixel(x,y,c) { //first thing is to make sure the //pixel is within range of the screen if(x<0 || x>screenW) return; if(y<0 || y>screenH) return; //make sure x and y are integers x=Math.floor(x); y=Math.floor(y); if(c>0) // set the pixel bit to 1 screen[Math.floor(x/8) + y*screenW/8] |= Math.pow(2,x%8); else // set the pixel bit to 0 screen[Math.floor(x/8) + y*screenW/8] &= 255-Math.pow(2,x%8); } //******************** //The variables we will be using //to keep track of our points //******************** var pointX = 0; var pointY = 0; var pointX2 = 0; var pointY2 = 0; var angle = 0; var angle2 = 0; var rad = Math.PI / 180.0; //******************** //our function to assemble //what will be shown to the user //******************** function draw() { //set entire screen to black ClearScreen(); //put the first pixel on the screen PutPixel(pointX,pointY,1); //update the point pointX+=Math.cos(angle*rad); pointY+=Math.sin(angle*rad); angle++; //check the point to make //sure it's not out of range angle %=360; if(pointX<0) pointX=screenW-pointX; if(pointY<0) pointY=screenH-pointY; if(pointX>screenW) pointX=pointX-screenW; if(pointY>screenH) pointY=pointY-screenH; //put the second pixel on the screen PutPixel(pointX2,pointY2,1); angle2+=10; pointX2=Math.cos(angle2*rad)*50+pointX; pointY2=Math.sin(angle2*rad)*50+pointY; angle2 %=360; } |

In the prior lesson we just filled the entire screen with random information. In this lesson we’re going to be moving two pixels around using some basic trig. As a result we need to implement a function that will clear the screen. We could simply set the old pixel positions to black and then draw the new pixel positions. This would seem to be faster. But, due to the math required to plot a pixel there’s not a signifant gain and it won’t be long before there are too many pixels being drawn to utilize this method. So we’re not going to stick with clearing the whole screen right from the beginning.

1 2 3 4 5 6 | function ClearScreen() { max=screenW/8 * screenH; for(x=0;x<max;x++) screen[x]=0; } |

In for loops, the conditional part of the function is evaluated every loop. This is why we precalculate the number of elements in the array. Divides are computationally expensive and if we didn’t precalculate, we’d be dividing and getting the same result 2400 times. Setting an array position to 0 is significantly faster than plotting a pixel. And since we’re working with monocrome, setting one element to 0 actually sets eight pixels to black.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | function PutPixel(x,y,c) { //first thing is to make sure the //pixel is within range of the screen if(x<0 || x>screenW) return; if(y<0 || y>screenH) return; //make sure x and y are integers x=Math.floor(x); y=Math.floor(y); if(c>0) // set the pixel bit to 1 screen[Math.floor(x/8) + y*screenW/8] |= Math.pow(2,x%8); else // set the pixel bit to 0 screen[Math.floor(x/8) + y*screenW/8] &= 255-Math.pow(2,x%8); } |

The function PutPixel takes three values. X is the horizontal position of the pixel, Y is the veritcal position of the pixel and C is the color of the pixel. There are then three sections to this function. The first section verifies that the X and Y values fall in the visible range of the screen. If they don’t then the function simply returns and the screen buffer isn’t changed.

The next section of code removes the decimal portion of the X and Y values. Since a screen cannot actually display fractional pixels we just ignore the fractional part. Math.floor is the JavaScript function to handle this operation. For example, if X is 5.9 then Math.floor(X) is equal to 5. If we were dealing with more colors we could use the fractional value to blend neighboring pixels. This is called “anti-aliasing.” It will be several lessons before we come back to that concept.

The final section of code for the PutPixel routine actually stores the pixel in the screen buffer. The first step is to find the section of 8 pixels that this pixel falls into.

1 | Math.floor(x/8) + y*screenW/8 |

We’re again using the floor function so that if for example we’re placing a pixel Y is 0 and X is 63 then 63/8 = 7.875. We can’t have a fractional position in an array so the floor value is 7. The second part of the equation finds the exact pixel we’re setting.

1 | Math.pow(2,x%8) |

63 mod 8 is equal to 7. This means we’re setting the 7th pixel in the 7th block of 8 pixels on the 0th row. The value of C determines how the second part is applied to the first part. We’re using binary operations since we’re working with bits. | is the binary OR and & is the binary AND. Rather than forcing the values 0 and 1 and doing an extra check we assume if C is 0 or less then the user is setting the pixel to black and if it’s greater than 0 they want to set it to white.

1 2 3 4 5 6 7 8 9 10 11 12 13 | Truth Table for AND 1 & 0 = 0 1 & 1 = 1 0 & 0 = 0 0 & 1 = 0 Truth Table for OR 1 | 0 = 1 1 | 1 = 1 0 | 0 = 0 0 | 1 = 1 |

Let’s assume the current block of 8 contains the values

1 2 | 0 1 2 3 4 5 6 7 1 0 1 0 1 0 1 0 |

and we want to set the 7th position to 1. We check the truth table and see that the only way to ensure that the final value is 1 is to OR the last position with the value 1.

Math.pow(2,x%8) where x%8 is 7 equals 128. 128 in binary is

1 2 | 0 1 2 3 4 5 6 7 0 0 0 0 0 0 0 1 |

Zeros don’t affect the original values when ORing. So if we OR 10101010 with 0000001 the result is 10101011 which is what we wanted.

If the 7th position were already 1 then it would simply remain 1 as seen by the truth table.

Now, let’s set that same pixel back to 0. We can’t OR a non zero value to zero. So what we need to do is AND our way back to a zero. If we were to AND 10101011 with 00000001 the result would be 00000001. When ANDing, 1s have no effect on the original values. This is the exact inverse of OR. So what we do is invert the binary values so we get 11111110. This is accomplished easily.

255-Math.pow(2,x%8)

255 – 128 = 127 = 11111110 in binary. We then perform the operation 10101011 & 11111110 and get 10101010 which is what we wanted.

Understanding how binary numbers work is important when programming. If we couldn’t perform binary operations it would either be necessary to enlarge the screen buffer array by a multiple of 8 and waste a lot of memory or do a lot of expensive math to decode the starting value into it’s component values and then reassemble it back to a single value. It’s best to learn the concepts of binary numbers doing something relativly simple than later for a more complicated algorithm. It’s just like phonics. You need to learn how the individual parts of the word work before you can understand the word. You need to learn how the basic components of the algorithm work before you can understand the algorithm as a whole.

Now that we can put pixels on the screen it’s time to do something a little more interesting with them. In this lesson we have 2 pixels moving around. The first uses the following math:

1 2 3 | pointX+=Math.cos(angle*rad); pointY+=Math.sin(angle*rad); angle++; |

rad is predefined as PI/180.0. Any time you have a value that requires calculation but doesn’t change it’s best to precalculate it and store it somewhere that the program has access to. With JavaScript the option is to make it a global. That entails defining the value outside of a function and then simply not changing it after it’s been calculated.

The net result of this simple math is that we move in the direction of angle where 0 is directly right, 90 is directly down(remember that SIN(90) is positive 1 and Y increases down the screen), 180 is directly left and 270 is directly up(remember that SIN(270) is -1 and Y decreases going up the screen). This forumla is used to move characters around the playing field.

1 2 3 4 | angle2+=10; pointX2=Math.cos(angle2*rad)*50+pointX; pointY2=Math.sin(angle2*rad)*50+pointY; angle2 %=360; |

Using these basic functions we now have a second point rotating around the first point with a radius of 50 pixels. The angle for this point changes 10 degrees every frame. With angles, COS relates to the horizontal position and SIN relates to the vertical position. If you’re getting some wierd results with rotations check your SIN and COS. It’s easy to get them mixed up if you’re not familiar with their function.

You’ll also notice that the pixel rotating around the other disappears when it goes outside the visible area rather than wrapping around like the first pixel. Because pixels will inevitably get out of range it’s not a good idea to build wrapping into the core pixel plotting function. It’s most often not the desired result of going off the screen.