The Render Process
The render process is what’s next, and it begins thusly:
for (let l: number = 0; l < state.layout.length; l++) {
xOffset = xOffset + tileShadowWidth;
yOffset = yOffset - tileShadowHeight;
const layer: number[][] = state.layout[l];
Chapter 11 time for fun: BattleJong, the Client
323
The overall flow here is that we’re going to iterate over the layers in the layout (which
can be found in the state object that we’ll look at later, but for now understand that
it’s simply the layout passed from the server in the same form that you saw in the last
chapter). Then, for each layer, we’ll iterate the rows in it. Then, for each tile in the row,
we’ll render it. The xOffset and yOffset will result in the tiles of each layer being shifted
right and up a little bit, enough so that the tiles overlap the previously rendered ones,
and we get the correct appearance from the board.
Next, after pulling the layer data out of the layout, we iterate the rows in it:
for (let r: number = 0; r < layer.length; r++) {
const row: number[] = layer[r];
for (let c: number = row.length; c >= 0; c--) {
let tileVal: number = row[c];
For each row, we iterate the tiles in it, and for each we grab its value. With that, we do
some logic:
if (tileVal > 0) {
const xLoc: number = ((c * tileWidth) - (c * tileShadowWidth)) +
xOffset + xAdjust;
const yLoc: number = ((r * tileHeight) - (r * tileShadowHeight)) +
yOffset + yAdjust;
First, only a value great than zero is rendered because zero means there is no tile
in a given grid slot, and negative one would mean the file has been cleared. So, we
need to figure out where to place the tile. Every tile will be positioned absolutely, so we
need to figure out the x and y location (left and top in style terms). Since what we’re
really rendering is a grid, the math is straightforward. For xLoc, it’s the column number
(variable c) multiplied by the width of the tile, which gives us a horizontal row of tiles.
Then, we have to account for the tile shadow since we want them to overlap, so we
subtract out the width of the shadow times the column again. Next, we need to account
for the 3D look of the grid. To do this, each layer shifts the tiles in it by a little bit up and
right. That’s where xOffset comes into play. Finally, with the location determined, we
need to ensure that the entire grid of tiles is pushed away from the upper-left border, so
xAdjust is added.
The vertical location yLoc is calculated in the same way but now using tile and
shadow height and the row number as a multiplier.
Chapter 11 time for fun: BattleJong, the Client
324
Now, I said there that each tile would be positioned absolutely, and this is where that
CSSProperties import from earlier is used:
const style: CSSProperties = {
position : "absolute",
left : `${xLoc}px`,
top : `${yLoc}px`
};
The issue being solved here is that when I render the tags, they’re all going
to have a similar style definition, just being different in the xLoc and yLoc values. So, it
makes sense to have that be a common element, a common object, which is what it is
here. However, the value of the style attribute, as you’ll see soon, can’t just be a string,
or even a plain old JavaScript object. It must be a CSSProperties instance, so that’s
the type used here and why it had to be imported. But, the type aside, it really is just an
object, nothing special otherwise.
Next, we have to account for the possibility that the tile is highlighted because
it’s selected. This is implemented by applying a style class to it. We know it must be
highlighted if its value is greater than 1000, so:
let className: string = "";
if (tileVal > 1000) {
className = "highlightTile";
tileVal = tileVal - 1000;
}
Of course, the values of our tiles are numbered 101–142, so in order to get the right
tile displayed, we need to subtract 1000 from tileVal.
Now, as for the highlightTile style class applied, that’s as follows:
.highlightTile {
-webkit-filter : drop-shadow(0px 0px 50px #ff0000)
contrast(150%) saturate(200%);
filter : drop-shadow(0px 0px 10px #ff0000)
contrast(150%) saturate(200%);
}
Chapter 11 time for fun: BattleJong, the Client
325
I apply a red drop shadow to the element, but with no horizontal or vertical offset
and a big blue, which gives the effect of a glowing edge all around it. I also bump the
contrast and saturation up so that it stands out a bit more. It’s a simple way to implement
a highlight but rather effective.
The final piece of the puzzle is actually to render the tiles:
switch (tileVal) {
case 101 : tiles.push( className={className}
onClick={()=>state.tileClick(l, r, c)} alt="" />); break;
case 102 : tiles.push( className={className}
onClick={()=>state.tileClick(l, r, c)} alt="" />); break;
...40 more...
}
For each of the 42 tile types, we have a case in a switch. They’re all the same save
for the value of the src attribute. Here, you can see how the CSSProperties style
object is used and how the src points to one of the imported image modules (see, I
told you we’re using those like code!). Each also gets the className, which is blank
for all but the highlighted tile, if any. Finally, each tile gets an onClick handler that
calls the tileClick() method in the state object, passing it the layer, row, and column
number of the tile. We’ll look at that in the section about the state object. Oh, the alt
attribute also has to be defined to avoid a compiler warning (or at least an IDE warning
in my IDE of choice).
Once all the tiles have been pushed into the tiles array, we can finally return the
output of this (implicit) render() method:
return ( { tiles });
Once again, React.Fragment is used to be the one component returned, but now
it has a child that is the tiles array. React will dutifully recognize that we want all the
elements in the array to be children of the React.Fragment and we’re good to go.
Chapter 11 time for fun: BattleJong, the Client
326
Note the imports and the switch statement frankly bother me because having
42 of each would usually be seen as redundant coding, and i would agree!
however, i see no way to do like you can in Java and do a star import to get all
the images imported in one line, nor do i see a way dynamically name the tile in
the tag. the type of src isn’t a string, so simple concatenation won’t do,
and i couldn’t see a way to reference it dynamically either (something like, maybe,
const s: any = tile[tileVal];). perhaps you’re more clever than i am
and can figure something out, and i wouldn’t mind hearing about it if you did – you
can teach me something! – but sometimes there is no elegant solution (at least no
obvious one), so you just gotta do what you gotta do.
Do'stlaringiz bilan baham: |