tag:blogger.com,1999:blog-91008247468005980842024-02-07T19:48:52.050-08:00zmxvUnknownnoreply@blogger.comBlogger29125tag:blogger.com,1999:blog-9100824746800598084.post-2233489753579863662018-10-25T17:46:00.001-07:002018-10-25T17:46:48.907-07:00Moving to MediumMedium's clean editor and social features have won me over.<br />
I'm posting new articles at <a href="https://medium.com/@zmxv">https://medium.com/@zmxv</a>.<br />
<br />
The first two posts are regarding open-source projects I recently published on Github:<br />
<br />
<ul>
<li><a href="https://medium.com/@zmxv/every-day-calendar-html5-edition-b5ea71cf59c2">A web version of Simone Giertz's Every Day Calendar</a></li>
<li><a href="https://medium.com/@zmxv/bitmexgo-a-golang-library-for-bitmex-rest-apis-a2761787834d">Bitmexgo, a Golang package to access Bitmex's REST APIs</a></li>
</ul>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-9100824746800598084.post-23855140697685904122016-01-14T00:21:00.000-08:002016-01-14T00:29:39.541-08:00Let's write a mobile game with React Native (Part 2)<p>This is the second part of my <a href="http://blog.zmxv.com/2016/01/lets-write-a-mobile-game-with-react-native.html">React Native tutorial</a> that shows you how to write a cross-platform mobile game.
In <a href="http://blog.zmxv.com/2016/01/lets-write-a-mobile-game-with-react-native.html">Part 1</a>, we've rendered a resolution-independent letter grid with custom glyphs.
We're going to add event handling and animations to enliven the app.
</p>
<p>The following guide builds on top of the <a href="https://github.com/zmxv/alpha-reflex/releases/tag/v0.1">v0.1</a> code release; the end result can be found in <a href="https://github.com/zmxv/alpha-reflex/releases/tag/v0.2">v0.2</a>.</p>
<h3>7. Touch event handling</h3>
<p>The conventional way to capture click events in React Native is to use four <code>Touchable*</code> components: <a href="https://facebook.github.io/react-native/docs/touchablehighlight.html#content">TouchableHighlight</a>, <a href="https://facebook.github.io/react-native/docs/touchablenativefeedback.html#content">TouchableActiveFeedback</a>, <a href="https://facebook.github.io/react-native/docs/touchableopacity.html#content">TouchableOpacity</a>, and <a href="https://facebook.github.io/react-native/docs/touchablewithoutfeedback.html#content">TouchableWithoutFeedback</a>.</p>
<p><code>TouchableActiveFeedback</code> is Android-only; <code>TouchableHighlight</code> often causes undesirable artifacts; <code>TouchableWithoutFeedback</code> should be used with caution as it provides no visual cues; in most cases such as creating a button, you should consider using <code>TouchableOpacity</code>.</p>
<p>Let's give it a try by importing the component from <code>React</code> in <code>boardview.js</code>.</p>
<pre style="background:#0c1021;color:#f8f8f8"><span style="color:#fbde2d">var</span> {
...
TouchableOpacity, <span style="color:#aeaeae">// <- New</span>
...
} <span style="color:#fbde2d">=</span> React;
</pre>
<p>We then extract a <code>renderTile</code> method from <code>renderTiles</code> and wrap each tile <code><View></code> inside a <code><TouchableOpacity></code>:</p>
<pre style="background:#0c1021;color:#f8f8f8">...
renderTiles() {
<span style="color:#fbde2d">var</span> result <span style="color:#fbde2d">=</span> [];
<span style="color:#fbde2d">for</span> (<span style="color:#fbde2d">var</span> row <span style="color:#fbde2d">=</span> <span style="color:#d8fa3c">0</span>; row <span style="color:#fbde2d"><</span> SIZE; row<span style="color:#fbde2d">++</span>) {
<span style="color:#fbde2d">for</span> (<span style="color:#fbde2d">var</span> col <span style="color:#fbde2d">=</span> <span style="color:#d8fa3c">0</span>; col <span style="color:#fbde2d"><</span> SIZE; col<span style="color:#fbde2d">++</span>) {
<span style="color:#fbde2d">var</span> key <span style="color:#fbde2d">=</span> row <span style="color:#fbde2d">*</span> SIZE <span style="color:#fbde2d">+</span> col;
<span style="color:#fbde2d">var</span> letter <span style="color:#fbde2d">=</span> <span style="color:#8da6ce">String</span>.<span style="color:#8da6ce">fromCharCode</span>(<span style="color:#d8fa3c">65</span> <span style="color:#fbde2d">+</span> key);
<span style="color:#fbde2d">var</span> position <span style="color:#fbde2d">=</span> {
left: col <span style="color:#fbde2d">*</span> CELL_SIZE <span style="color:#fbde2d">+</span> CELL_PADDING,
top: row <span style="color:#fbde2d">*</span> CELL_SIZE <span style="color:#fbde2d">+</span> CELL_PADDING
};
result.<span style="color:#8da6ce">push</span>(this.renderTile(key, position, letter)); <span style="color:#aeaeae">// <- New</span>
}
}
<span style="color:#fbde2d">return</span> result;
},
<span style="color:#aeaeae">// New</span>
renderTile(id, position, letter) {
<span style="color:#fbde2d">return</span> <span style="color:#fbde2d"><</span>TouchableOpacity key<span style="color:#fbde2d">=</span>{id}
onPress<span style="color:#fbde2d">=</span>{() <span style="color:#fbde2d">=</span><span style="color:#fbde2d">></span> <span style="color:#ff6400">console</span><span style="color:#8da6ce">.log</span>(id)}<span style="color:#fbde2d">></span>
<span style="color:#fbde2d"><</span>View style<span style="color:#fbde2d">=</span>{[styles.tile, position]}<span style="color:#fbde2d">></span>
<span style="color:#fbde2d"><</span><span style="color:#8da6ce">Text</span> style<span style="color:#fbde2d">=</span>{styles.letter}<span style="color:#fbde2d">></span>{letter}<span style="color:#fbde2d"><</span>/<span style="color:#8da6ce">Text</span><span style="color:#fbde2d">></span>
<span style="color:#fbde2d"><</span>/View<span style="color:#fbde2d">></span>
<span style="color:#fbde2d"><</span>/TouchableOpacity<span style="color:#fbde2d">></span>;
},
...
</pre>
<p>Notice that each <code><TouchableOpacity></code> has a unique <code>key</code> property because they all nest under the same parent node. As mentioned in Part 1 Section 5, this helps React Native to efficiently compare virtual DOM trees. The inner <code><View></code> and <code><Text></code> don't need keys because they have no sibling nodes at all — it doesn't hurt if you set keys, though.</p>
<p>The <code>onPress</code> property uses ES6's arrow syntax to declare an anonymous, parameterless event handler. For now, it logs the tile id for debugging; our real game logic will be filled in later.</p>
<p>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEv3dEkRCGOnZI12wYdckAR1Lo1S6IebEop35xWUght86HJa3blY80N8PF70dfU7EGir-hu237Ebhyphenhyphen2Os4lMu2pjhpjzhNsOrn4rTkCioebOS0cCJLdp8DstnE_gh_hQHnCjPRdAp4Kfs/s1600/touchableopacity.gif" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEv3dEkRCGOnZI12wYdckAR1Lo1S6IebEop35xWUght86HJa3blY80N8PF70dfU7EGir-hu237Ebhyphenhyphen2Os4lMu2pjhpjzhNsOrn4rTkCioebOS0cCJLdp8DstnE_gh_hQHnCjPRdAp4Kfs/s1600/touchableopacity.gif" /></a></p>
<p>Play with the app on a device and you'll observe two flaws:</p>
<ol>
<li>The <code>onPress</code> event handler isn't triggered until you lift your finger (see the screencast above). This behavior is not bad for regular apps because it allows users to easily cancel a click by sliding outside the click target. However, responsiveness is paramount in a game, so we'd like to fire the event handler as soon as a touch starts. You could solve this by binding an <code>onPressIn</code> handler rather than an <code>onPress</code>, yet it won't fix the next issue.</li>
<li>If you press a tile and hold your finger, then press another tile, the second click won't register. Again, this behavior is perfectly fine for regular apps, but in our fast-paced game, it'll cause tremendous frustration when a player taps with multiple fingers at high speed.
</li>
</ol>
<p>A quick solution that kills two birds with one stone is to get rid of the <code>TouchableOpacity</code> wrapper and directly attach an <code>onStartShouldSetResponder</code> handler to each tile <code><View></code>. The handler should always return <code>false</code> or a false-y value like <code>undefined</code>, so that it never nominates itself as the event responder. Our new <code>renderTile</code> method looks like this:</p>
<pre style="background:#0c1021;color:#f8f8f8">...
renderTile(id, position, letter) {
<span style="color:#fbde2d">return</span> <span style="color:#fbde2d"><</span>View key<span style="color:#fbde2d">=</span>{id} style<span style="color:#fbde2d">=</span>{[styles.tile, position]}
onStartShouldSetResponder<span style="color:#fbde2d">=</span>{() <span style="color:#fbde2d">=</span><span style="color:#fbde2d">></span> this.clickTile(id)}<span style="color:#fbde2d">></span>
<span style="color:#fbde2d"><</span><span style="color:#8da6ce">Text</span> style<span style="color:#fbde2d">=</span>{styles.letter}<span style="color:#fbde2d">></span>{letter}<span style="color:#fbde2d"><</span>/<span style="color:#8da6ce">Text</span><span style="color:#fbde2d">></span>
<span style="color:#fbde2d"><</span>/View<span style="color:#fbde2d">></span>;
},
clickTile(id) {
<span style="color:#ff6400">console</span><span style="color:#8da6ce">.log</span>(id);
},
...
</pre>
<p>As the following screencast demonstrates, each click now immediately triggers the <code>clickTile</code> event handler. You may also verify on a device that no clicks are lost when multi-touch is involved.</p>
<p>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjk8gJJP6s5cbKOMZsYU0LBu9miJ-3f-Q_JHLoyMKzRNf15dqr4i5H1mkNPMEd-1VGSBr6oZGOP_8TeUiOuAa5tesgsxPxWw9fvzVep6_vlzfrjDRxjwzL51hzg53WLNFzidXFUz9wpsZk/s1600/onstartshouldsetresponder.gif" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjk8gJJP6s5cbKOMZsYU0LBu9miJ-3f-Q_JHLoyMKzRNf15dqr4i5H1mkNPMEd-1VGSBr6oZGOP_8TeUiOuAa5tesgsxPxWw9fvzVep6_vlzfrjDRxjwzL51hzg53WLNFzidXFUz9wpsZk/s1600/onstartshouldsetresponder.gif" /></a></p>
<p>The only thing missing is some form of visual feedback to highlight the tile being pressed. We're going to fix that with animations.</p>
<h3>8. Property animation</h3>
<p>React Native provides a convenient <code>Animated</code> module for animating component properties. It's designed in such a way that our rendering logic can remain largely intact when we plug in the animation logic. Let's add a simple opacity animation to see how it works.</p>
<p>First, import <code>Animated</code> and <code>Easing</code>:</p>
<pre style="background:#0c1021;color:#f8f8f8"><span style="color:#fbde2d">var</span> {
Animated, <span style="color:#aeaeae">// <- New</span>
Easing, <span style="color:#aeaeae">// <- New</span>
...
} <span style="color:#fbde2d">=</span> React;
</pre>
<p>Add a <code>getInitialState()</code> method to the <code>BoardView</code> component, where we initialize 16 (4x4) <code>Animated.Value</code> instances, each controlling the opacity of a single tile. Pass <code>1</code> to the constructor of <code>Animated.Value</code> so that all letter tiles are fully opaque at the start.</p>
<pre style="background:#0c1021;color:#f8f8f8"><span style="color:#fbde2d">var</span> BoardView <span style="color:#fbde2d">=</span> React.createClass({
getInitialState() { <span style="color:#aeaeae">// New method</span>
<span style="color:#fbde2d">var</span> opacities <span style="color:#fbde2d">=</span> <span style="color:#fbde2d">new</span> <span style="color:#ff6400">Array</span>(SIZE <span style="color:#fbde2d">*</span> SIZE);
<span style="color:#fbde2d">for</span> (<span style="color:#fbde2d">var</span> i <span style="color:#fbde2d">=</span> <span style="color:#d8fa3c">0</span>; i <span style="color:#fbde2d"><</span> opacities.<span style="color:#8da6ce">length</span>; i<span style="color:#fbde2d">++</span>) {
opacities[i] <span style="color:#fbde2d">=</span> <span style="color:#fbde2d">new</span> <span style="color:#ff6400">Animated.Value</span>(<span style="color:#d8fa3c">1</span>);
}
<span style="color:#fbde2d">return</span> {opacities}; <span style="color:#aeaeae">// ES6 shorthand for {opacities: opacities}</span>
},
...
</pre>
<p>Once React Native mounts the component, individual <code>Animated.Value</code> instances can be accessed by <code>this.state.opacities[<i>id</i>]</code>. Hook them up with the style object of letter tiles as shown below:</p>
<pre style="background:#0c1021;color:#f8f8f8"> renderTiles() {
<span style="color:#fbde2d">var</span> result <span style="color:#fbde2d">=</span> [];
<span style="color:#fbde2d">for</span> (<span style="color:#fbde2d">var</span> row <span style="color:#fbde2d">=</span> <span style="color:#d8fa3c">0</span>; row <span style="color:#fbde2d"><</span> SIZE; row<span style="color:#fbde2d">++</span>) {
<span style="color:#fbde2d">for</span> (<span style="color:#fbde2d">var</span> col <span style="color:#fbde2d">=</span> <span style="color:#d8fa3c">0</span>; col <span style="color:#fbde2d"><</span> SIZE; col<span style="color:#fbde2d">++</span>) {
<span style="color:#fbde2d">var</span> id <span style="color:#fbde2d">=</span> row <span style="color:#fbde2d">*</span> SIZE <span style="color:#fbde2d">+</span> col;
<span style="color:#fbde2d">var</span> letter <span style="color:#fbde2d">=</span> <span style="color:#8da6ce">String</span>.<span style="color:#8da6ce">fromCharCode</span>(<span style="color:#d8fa3c">65</span> <span style="color:#fbde2d">+</span> id);
<span style="color:#fbde2d">var</span> style <span style="color:#fbde2d">=</span> {
left: col <span style="color:#fbde2d">*</span> CELL_SIZE <span style="color:#fbde2d">+</span> CELL_PADDING,
top: row <span style="color:#fbde2d">*</span> CELL_SIZE <span style="color:#fbde2d">+</span> CELL_PADDING,
opacity: this.state.opacities[id], <span style="color:#aeaeae">// <- New</span>
};
result.<span style="color:#8da6ce">push</span>(this.renderTile(id, style, letter));
}
}
<span style="color:#fbde2d">return</span> result;
},
renderTile(id, style, letter) {
<span style="color:#aeaeae">// v- New</span>
<span style="color:#fbde2d">return</span> <span style="color:#fbde2d"><</span>Animated.View key<span style="color:#fbde2d">=</span>{id} style<span style="color:#fbde2d">=</span>{[styles.tile, style]}
onStartShouldSetResponder<span style="color:#fbde2d">=</span>{() <span style="color:#fbde2d">=</span><span style="color:#fbde2d">></span> this.clickTile(id)}<span style="color:#fbde2d">></span>
<span style="color:#fbde2d"><</span><span style="color:#8da6ce">Text</span> style<span style="color:#fbde2d">=</span>{styles.letter}<span style="color:#fbde2d">></span>{letter}<span style="color:#fbde2d"><</span>/<span style="color:#8da6ce">Text</span><span style="color:#fbde2d">></span>
<span style="color:#fbde2d"><</span>/Animated.View<span style="color:#fbde2d">></span>;
},
</pre>
<p>Make sure that you change <code><View></code> to <code>Animated.View</code> where <code>Animated.Value</code>s can be applied to. Otherwise, you'll run into a scary, red screen of error that isn't particularly informative:</p>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQ91CRyt1pH-CbzFXEInaYPatgvUNYOwsa-iArU6gmXNhhhDt2W2kDg1Gis-lPmJX-UwP-2I7oki6hG4UTe_5zceu4JGse4I5J1jEg7VQ9D2CyqqcGPtrzo6X7rh8tfYyRr13GV_2jXr0/s1600/alpha-41-animated-warning.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQ91CRyt1pH-CbzFXEInaYPatgvUNYOwsa-iArU6gmXNhhhDt2W2kDg1Gis-lPmJX-UwP-2I7oki6hG4UTe_5zceu4JGse4I5J1jEg7VQ9D2CyqqcGPtrzo6X7rh8tfYyRr13GV_2jXr0/s400/alpha-41-animated-warning.png" /></a>
<p>Similarly, you can animate <code>Text</code> with <code>Animated.Text</code>, <code>Image</code> with <code>Animated.Image</code>, or custom components created with <code>createAnimatedComponent</code>. At this stage, nothing is actually animated yet. We'll kick off the opacity animation in the <code>clickTile</code> event handler using the <code>Animated.timing</code> API:</p>
<pre style="background:#0c1021;color:#f8f8f8"> clickTile(id) {
<span style="color:#fbde2d">var</span> opacity <span style="color:#fbde2d">=</span> this.state.opacities[id];
opacity.setValue(.<span style="color:#d8fa3c">5</span>); <span style="color:#aeaeae">// half transparent, half opaque</span>
Animated.timing(opacity, {
toValue: <span style="color:#d8fa3c">1</span>, <span style="color:#aeaeae">// fully opaque</span>
duration: <span style="color:#d8fa3c">250</span>, <span style="color:#aeaeae">// milliseconds</span>
}).<span style="color:#8da6ce">start</span>();
},
</pre>
<p>With these simple changes, we have achieved the following effect:</p>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAYdfFRYjUYLmgccMp503Q7o-bduC8zcdpTNwE2lFs7wtgr7jTWghDyJL6T8_bZBLyKH1ejSW7jm4UigWDNo1FmfyaQ2CYqL5Uhe30WNkIFYVy_4KITLROxOFYX6EtLbbizOmXZePvswk/s1600/opacity-animation.gif" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAYdfFRYjUYLmgccMp503Q7o-bduC8zcdpTNwE2lFs7wtgr7jTWghDyJL6T8_bZBLyKH1ejSW7jm4UigWDNo1FmfyaQ2CYqL5Uhe30WNkIFYVy_4KITLROxOFYX6EtLbbizOmXZePvswk/s1600/opacity-animation.gif" /></a>
<p><code>Animated.timing</code> gives us two additional options to fine-tune the animation: <code>easing</code> (custom easing function) and <code>delay</code> (delay in milliseconds). You may also want to play with <code>Animated.spring</code> (bouncing animation) or orchestrate multiple animations with <code>Animated.sequence</code> (sequential animation) and <code>Animated.parallel</code> (simultaneous animation).</p>
<p>Faded opacity is a tad boring. Let's replace it with a 3D tilting effect as if tiles revolve around the X-axis. That's right — we can do simple 3D transformations in React Native!</p>
<p>First rename the <code>Animated.Value</code>s and initialize them with <code>0</code>:</p>
<pre style="background:#0c1021;color:#f8f8f8"> getInitialState() {
<span style="color:#fbde2d">var</span> tilt <span style="color:#fbde2d">=</span> <span style="color:#fbde2d">new</span> <span style="color:#ff6400">Array</span>(SIZE <span style="color:#fbde2d">*</span> SIZE);
<span style="color:#fbde2d">for</span> (<span style="color:#fbde2d">var</span> i <span style="color:#fbde2d">=</span> <span style="color:#d8fa3c">0</span>; i <span style="color:#fbde2d"><</span> tilt.<span style="color:#8da6ce">length</span>; i<span style="color:#fbde2d">++</span>) {
tilt[i] <span style="color:#fbde2d">=</span> <span style="color:#fbde2d">new</span> <span style="color:#ff6400">Animated.Value</span>(<span style="color:#d8fa3c">0</span>);
}
<span style="color:#fbde2d">return</span> {tilt};
},
</pre>
<p>Then replace <code>opacity</code> in <code>style</code> with a <code>transform</code> — an array of transformation objects:
<ul>
<li><code>{perspective: ...}</code> declares the virtual distance from the viewing point to the <code>z=0</code> plane. Use it to control the intensity of 3D effect: the greater the value is, the further away you are from the objects, the less intense the distortion appears (i.e. objects look more flat).</li>
<li><code>{rotateX: ...}</code> sets the rotational degrees around the X-axis, creating a tilting effect. Due to an unfortunate API design (as of RN 0.18), all the rotational properties (<code>rotate</code>, <code>rotateX</code>, <code>rotateY</code>, and <code>rotateZ</code>) only take a string (e.g. <code>'30deg'</code>). We can't directly apply a numerical degree or radian value, so we resort to <code>Animated.Value</code>'s <code>interpolate</code> function to map a floating point number to a string.</li>
</ul>
</p>
<pre style="background:#0c1021;color:#f8f8f8"> renderTiles() {
<span style="color:#fbde2d">var</span> result <span style="color:#fbde2d">=</span> [];
<span style="color:#fbde2d">for</span> (<span style="color:#fbde2d">var</span> row <span style="color:#fbde2d">=</span> <span style="color:#d8fa3c">0</span>; row <span style="color:#fbde2d"><</span> SIZE; row<span style="color:#fbde2d">++</span>) {
<span style="color:#fbde2d">for</span> (<span style="color:#fbde2d">var</span> col <span style="color:#fbde2d">=</span> <span style="color:#d8fa3c">0</span>; col <span style="color:#fbde2d"><</span> SIZE; col<span style="color:#fbde2d">++</span>) {
<span style="color:#fbde2d">var</span> id <span style="color:#fbde2d">=</span> row <span style="color:#fbde2d">*</span> SIZE <span style="color:#fbde2d">+</span> col;
<span style="color:#fbde2d">var</span> letter <span style="color:#fbde2d">=</span> <span style="color:#8da6ce">String</span>.<span style="color:#8da6ce">fromCharCode</span>(<span style="color:#d8fa3c">65</span> <span style="color:#fbde2d">+</span> id);
<span style="color:#fbde2d">var</span> tilt <span style="color:#fbde2d">=</span> this.state.tilt[id].interpolate({
inputRange: [<span style="color:#d8fa3c">0</span>, <span style="color:#d8fa3c">1</span>],
outputRange: [<span style="color:#61ce3c">'0deg'</span>, <span style="color:#61ce3c">'-30deg'</span>]
});
<span style="color:#fbde2d">var</span> style <span style="color:#fbde2d">=</span> {
left: col <span style="color:#fbde2d">*</span> CELL_SIZE <span style="color:#fbde2d">+</span> CELL_PADDING,
top: row <span style="color:#fbde2d">*</span> CELL_SIZE <span style="color:#fbde2d">+</span> CELL_PADDING,
transform: [{perspective: CELL_SIZE <span style="color:#fbde2d">*</span> <span style="color:#d8fa3c">8</span>},
{rotateX: tilt}]
};
result.<span style="color:#8da6ce">push</span>(this.renderTile(id, style, letter));
}
}
<span style="color:#fbde2d">return</span> result;
},
</pre>
<p>The final step is to patch the <code>clickTile</code> method to set up the new animation.</p>
<pre style="background:#0c1021;color:#f8f8f8"> clickTile(id) {
<span style="color:#fbde2d">var</span> tilt <span style="color:#fbde2d">=</span> this.state.tilt[id];
tilt.setValue(<span style="color:#d8fa3c">1</span>); <span style="color:#aeaeae">// mapped to -30 degrees</span>
Animated.timing(tilt, {
toValue: <span style="color:#d8fa3c">0</span>, <span style="color:#aeaeae">// mapped to 0 degrees (no tilt)</span>
duration: <span style="color:#d8fa3c">250</span>, <span style="color:#aeaeae">// milliseconds</span>
easing: Easing.quad <span style="color:#aeaeae">// quadratic easing function: (t) => t * t</span>
}).<span style="color:#8da6ce">start</span>();
},
</pre>
<p>Technically, this 3D effect is not accurate — it's a mix of orthogonal projection and perspective projection. If you aspire to render a photorealistic scene with fancy shaders, I'd suggest that you take a look at <a href="https://github.com/ProjectSeptemberInc/gl-react-native/">gl-react-native</a> — wait, no, please please use a real 3D engine. For our little game, I'd say this faux-3D animation is acceptable:</p>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4JpMvMFtTUcnMoJIIhPWP3SHi3sygrMF4nZo1-VMMhHtQ7xQ4bbe5XvDU5QjolkLp9WOX2069rKqHU71iEpU68t1E-o-SJtrKw3gJY6VcTpJrje2rUfiJhcY3niLghAHhBNHtE_n39CI/s1600/alpha-42-3d-animation.gif" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4JpMvMFtTUcnMoJIIhPWP3SHi3sygrMF4nZo1-VMMhHtQ7xQ4bbe5XvDU5QjolkLp9WOX2069rKqHU71iEpU68t1E-o-SJtrKw3gJY6VcTpJrje2rUfiJhcY3niLghAHhBNHtE_n39CI/s1600/alpha-42-3d-animation.gif" /></a>
<p>Full source up to this point can be found at <a href="https://github.com/zmxv/alpha-reflex/releases/tag/v0.2">https://github.com/zmxv/alpha-reflex/releases/tag/v0.2</a>. In the next article, we'll create a timer component using a different animation technique and implement some game-specific logic.</p>Unknownnoreply@blogger.com14tag:blogger.com,1999:blog-9100824746800598084.post-57748867062422833252016-01-09T19:19:00.000-08:002016-01-14T00:22:57.757-08:00Let's write a mobile game with React Native<h3>1. Introduction</h3>
<p>This is the first part of a tutorial that will show you how to write a cross-platform mobile game with React Native.
We're going to replicate my casual game "<a href="/2016/01/alpha-reflex.html">Alpha Reflex</a>" which can be freely downloaded from <a href="https://itunes.apple.com/us/app/alpha-reflex/id1071591514?ls=1&mt=8">the App Store</a> and <a href="https://play.google.com/store/apps/details?id=com.pretonic.alphareflex">the Play Store</a>. The game challenges players to find randomized letters in alphabetical order as fast as possible.</p>
<div class="separator" style="clear: both; text-align: center;margin:32px auto;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7uN6KMqnhPS1B4E4PAiiU56JuemxuKw1s114lHSGwQvhLYNPOUv6ykeHnMCHXeptG2lvPMGDd9pf4eB54_s-Ptah_V2-u3AqaiNKppTsPuN58hKT1vEUFBD8zxxVu4nuI_FvKH4f0cBM/s1600/win-640x960.png" imageanchor="1" style="margin-left: .5em; margin-right: .5em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7uN6KMqnhPS1B4E4PAiiU56JuemxuKw1s114lHSGwQvhLYNPOUv6ykeHnMCHXeptG2lvPMGDd9pf4eB54_s-Ptah_V2-u3AqaiNKppTsPuN58hKT1vEUFBD8zxxVu4nuI_FvKH4f0cBM/s200/win-640x960.png" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguEURfiIGiX15jNIX6r8Gt2W83DXSh29cLO-QQ85tzkf9uYaMvkIQd3b5F_BS7qGYfJTiP74Ai2_AUUfqKmGJ0YR0zumlMp3tnZc0ugNN0Gjy-0DTF0u1345pL6LbZgZ_SbFBSfbrqdB8/s1600/english-640x960.png" imageanchor="1" style="margin-left: .5em; margin-right: .5em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguEURfiIGiX15jNIX6r8Gt2W83DXSh29cLO-QQ85tzkf9uYaMvkIQd3b5F_BS7qGYfJTiP74Ai2_AUUfqKmGJ0YR0zumlMp3tnZc0ugNN0Gjy-0DTF0u1345pL6LbZgZ_SbFBSfbrqdB8/s200/english-640x960.png" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgI85KjMlyoTFltJ4sCSkDZPXapPRWCcL7o84MQMXKNlini6j0Ax93cVKAFI4mWa1q90chXO-uDPJ8Q907DBamTW35cKc0OkRrFb4ckhxPSoFdn8Y7eW7d2SNybSoSk4L__3P9MbpO0bnY/s1600/hiragana-640x960.png" imageanchor="1" style="margin-left: .5em; margin-right: .5em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgI85KjMlyoTFltJ4sCSkDZPXapPRWCcL7o84MQMXKNlini6j0Ax93cVKAFI4mWa1q90chXO-uDPJ8Q907DBamTW35cKc0OkRrFb4ckhxPSoFdn8Y7eW7d2SNybSoSk4L__3P9MbpO0bnY/s200/hiragana-640x960.png" /></a></div>
<p>React Native isn't really designed for games. There exists a myriad of better tools for professional game development: Unity, libgdx, cocos2d-x, Moai, Starling, LÖVE, etc. So why are we creating a mobile game with React Native? First off, even simple games are more fun to develop than yet another To-Do app. Games also push for a wide range of transferable skills that will help you develop other React Native apps more effectively. In this series, we'll cover native module development, 2d/3d animations, custom event handling, cross-platform considerations, and many other topics.</p>
<p>Source code for this tutorial can be found at <a href="https://github.com/zmxv/alpha-reflex">https://github.com/zmxv/alpha-reflex</a>, though I'd suggest that you follow the guide to remake the app from scratch to gain some hands-on practice.
<h3>2. App set-up</h3>
<p>If you haven't installed the React Native command line tool, please follow the instructions at <a href="https://facebook.github.io/react-native/docs/getting-started.html">https://facebook.github.io/react-native/docs/getting-started.html</a> to get started.</p>
<p>Ready? Now run <code>$ react-native init AlphaReflex</code> from the command line. This creates a skeleton app that displays a welcome screen.</p>
<div class="separator" style="clear: both; text-align: center;margin:32px auto;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtpBk1B5EH_RZIqXwLoKS4uGKHMCfQitgcSKad471hQacosVZjl3zZOYGgP4Br88km2_097uEltXok7YqJkJjvp-IfQ5WBwZku_o4F-_733XUJ8nW4kq8zzPLhZQC8bGq-Gn7ZZ-h3eh8/s1600/alpha-init-ios.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitgunVLkEMxwgl7sWtXjb1BYNVLZrISYKRO7bys4KiZ5HSou9LxjGaGh3PyMRxbIa7afUM4KoWoGizQN2W43P8CIV8DpphKtz4JSI9Tl91mTweghdhgnpzDbuo3saJqyINLBjmliOnRYo/s320/alpha-init-ios.png" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEht1LGwuautCGl32t_hXcVQ7vvnvUwIC9puuRWnkm-v9-nGupMUWPc1fcRmsr0B1E7hAXVLARgW4yWacrWuOS-EVvEp0igEvWsbReVfyXXde_rr59pmotTgmxRgh_4UhiPP_70f1ej8eA8/s1600/alpha-init-and.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFfxcCsB0hWLk-mc8SeCNpa6VE4LsdI7_dgEnyPH7Fzt_xgzkOeA7TrKxhSTxt9LuUpOxedjF_W8F5ByBBJE7EDZaXakf460mvf2ILexA5GSsErigJbQ7mV8tJw9VRLykefGbK-Vbz0Ss/s320/alpha-init-and.jpg" /></a></div>
<p>To test the iOS app, open <code>alpha/ios/alpha.xcodeproj</code> in Xcode and hit ⌘+R. To run the Android version, you'll first need to either start an emulator or connect a device before executing <code>$ react-native run-android</code>.</p>
<p>Android's <a href="http://developer.android.com/tools/devices/emulator.html">emulator</a> is excruciatingly slow. Installing Intel's HAXM alleviates the pain a little bit, but you're better off with a physical test device connected to your computer. If you're using OS X, I recommend that you develop apps for iOS first and verify Android compatibility later. It'll save you a lot of time on thumb-twiddling.</p>
<p>Okay, now is a good time to take a snapshot of your code in a version control system. For example:</p>
<p><pre>git init
git add .
git commit -m 'Initial commit'</pre></p>
<h3>3. Entry files</h3>
<p>Under the app directory, you'll find two auto-generated JavaScript files: <code>index.ios.js</code> and <code>index.android.js</code>. They are the entry points for iOS and Android respectively. To reuse as much code as possible for both platforms, we should minimize the amount of logic there. I suggest that you only register an app entry class in these two files:</p>
<p><pre style="background:#0c1021;color:#f8f8f8">require(<span style="color:#61ce3c">'react-native'</span>).AppRegistry.registerComponent(<span style="color:#61ce3c">'AlphaReflex'</span>,
() <span style="color:#fbde2d">=</span><span style="color:#fbde2d">></span> require(<span style="color:#61ce3c">'./main.js'</span>));
</pre>
</p>
<p>We then create a real, shared entry class in a <code>main.js</code> file:</p>
<p><pre style="background:#0c1021;color:#f8f8f8"><span style="color:#61ce3c">'use strict'</span>;
<span style="color:#fbde2d">var</span> React <span style="color:#fbde2d">=</span> require(<span style="color:#61ce3c">'react-native'</span>);
<span style="color:#fbde2d">var</span> {
StyleSheet,
<span style="color:#8da6ce">Text</span>,
View,
} <span style="color:#fbde2d">=</span> React;
<span style="color:#fbde2d">var</span> Main <span style="color:#fbde2d">=</span> React.createClass({
render() {
<span style="color:#fbde2d">return</span> <span style="color:#fbde2d"><</span>View style<span style="color:#fbde2d">=</span>{styles.container}<span style="color:#fbde2d">></span>
<span style="color:#fbde2d"><</span>View style<span style="color:#fbde2d">=</span>{styles.tile}<span style="color:#fbde2d">></span>
<span style="color:#fbde2d"><</span><span style="color:#8da6ce">Text</span> style<span style="color:#fbde2d">=</span>{styles.letter}<span style="color:#fbde2d">></span>A<span style="color:#fbde2d"><</span>/<span style="color:#8da6ce">Text</span><span style="color:#fbde2d">></span>
<span style="color:#fbde2d"><</span>/View<span style="color:#fbde2d">></span>
<span style="color:#fbde2d"><</span>/View<span style="color:#fbde2d">></span>;
},
});
<span style="color:#fbde2d">var</span> styles <span style="color:#fbde2d">=</span> StyleSheet.create({
container: {
flex: <span style="color:#d8fa3c">1</span>,
justifyContent: <span style="color:#61ce3c">'center'</span>,
alignItems: <span style="color:#61ce3c">'center'</span>,
backgroundColor: <span style="color:#61ce3c">'#644B62'</span>,
},
tile: {
width: <span style="color:#d8fa3c">100</span>,
height: <span style="color:#d8fa3c">100</span>,
borderRadius: <span style="color:#d8fa3c">8</span>,
justifyContent: <span style="color:#61ce3c">'center'</span>,
alignItems: <span style="color:#61ce3c">'center'</span>,
backgroundColor: <span style="color:#61ce3c">'#BEE1D2'</span>,
},
letter: {
color: <span style="color:#61ce3c">'#333'</span>,
fontSize: <span style="color:#d8fa3c">80</span>,
},
});
module.exports <span style="color:#fbde2d">=</span> Main;
</pre></p>
<p><code>Main</code> is our first custom UI component. For now, it simply displays a lonely letter tile in its <code>render()</code> method.</p>
<div class="separator" style="clear: both; text-align: center;margin:32px auto;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEji96L8UFFU492DRJrqWROoBb28Gu6vm-0SgxXwmRIxVrRF3tH3ALbF29ag4EzSb4wyCAEKv1IqbMFICHx9JUneSonWfaakwemp2mpDhEEEgTs-QUIX1_DeacaTykcEyOX4X1nJ7ry5e7k/s1600/alpha-11-tile.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEji96L8UFFU492DRJrqWROoBb28Gu6vm-0SgxXwmRIxVrRF3tH3ALbF29ag4EzSb4wyCAEKv1IqbMFICHx9JUneSonWfaakwemp2mpDhEEEgTs-QUIX1_DeacaTykcEyOX4X1nJ7ry5e7k/s400/alpha-11-tile.png" /></a></div>
<p>Alternatively, you may write <code>class Main extends React.Component</code> to declare the <code>Main</code> component. However, the ES6 class syntax makes it harder to define mix-ins and static properties; the code is also slightly longer. We'll therefore stick to <code>React.createClass</code>.</p>
<h3>4. Flexbox</h3>
<p>React Native's flexbox layout model borrows ideas and terms from the CSS counterpart. In our <code>main.js</code> file, the <code>flex: 1</code> declaration stretches the <code>container</code> class to fill the entire screen. If you throw in a sibling <code><View></code> with the same style, each <code><View></code> will get half of the screen real estate because 1:1 equals 50%:50%. To split the screen into one-third and two-thirds parts, you'll need to style them with <code>flex: 1</code> and <code>flex: 2</code>.</p>
<div class="separator" style="clear: both; text-align: center;margin:32px auto;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0XnLpUm8Qw7ZTlsU0Kx_qYwHnZw2V7gS4L8fljE8r2zu19PFA7rPznvnI1lRGzjqGsrYVejbFcbE2giGGiXtM763aU6hEQejclN8jwLuOWowweIxh20joGrJvSCH3ptLev4W5Cohw-YE/s1600/flexbox-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0XnLpUm8Qw7ZTlsU0Kx_qYwHnZw2V7gS4L8fljE8r2zu19PFA7rPznvnI1lRGzjqGsrYVejbFcbE2giGGiXtM763aU6hEQejclN8jwLuOWowweIxh20joGrJvSCH3ptLev4W5Cohw-YE/s1600/flexbox-1.png" /></a></div>
<p>The <code>flexDirection</code> property controls the direction of child element flow, which is column-wise by default. Change it to <code>'row'</code> if you want a horizontal arrangement.</p>
<p><code>justifyContent</code> justifies elements along the primary flex direction; <code>alignItems</code> does the same job along the perpendicular axis. In the previous example, if you want to snap the letter tile to the left edge while keeping it vertically centered, you may change <code>container</code>'s <code>alignItems</code> to <code>'flex-start'</code> or simply comment out that line because <code>'flex-start'</code> is the default value. The result is shown below:</p>
<div class="separator" style="clear: both; text-align: center;margin:32px auto;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjk985UdNvIfw7kw7gJdPxAsAH4h7elBX0NLCfZ_VEmkv2KV9B-146XJVldFhAPLt_G20TpdkPCswD4Q5cej9JqdOSY9gLZwSkyrt6t7DgJuH-B7-bPuTuwgA_VnfTCuOzkHuZk5IkyEIY/s1600/alpha-12-tile-align.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjk985UdNvIfw7kw7gJdPxAsAH4h7elBX0NLCfZ_VEmkv2KV9B-146XJVldFhAPLt_G20TpdkPCswD4Q5cej9JqdOSY9gLZwSkyrt6t7DgJuH-B7-bPuTuwgA_VnfTCuOzkHuZk5IkyEIY/s320/alpha-12-tile-align.png" /></a></div>
<p>In general, the flexbox model is quite useful for designing adaptive layouts as it frees us from manually computing the exact bounds of UI elements. Yet we'll sometimes do the calculation ourselves and embrace <code>position: 'absolute'</code> when it comes to dynamic layouts.</p>
<h3>5. Resolution-independent rendering</h3>
<p>Next, we're going to create another React Native component that renders a 4x4 grid in a resolution-independent manner. Resolution independence is a nice property that scales our UI to fit any size so it won't leave excessive whitespace on large screens. To achieve that, there're generally two strategies:</p>
<ol>
<li>Dynamically set the size of UI widgets in proportion to the target screen dimension</li>
<li>Pick a "design size" (e.g. 640x960), hardcode the dimension and position of UI widgets inside that box, globally scale the box and its child elements to fit the screen</li>
</ol>
<p>The second approach is easier to work with, although it may lead to blurred results on large screens. It also lacks flexibility in fixed positioning. For our game, we'll go with the first approach and calculate appropriate UI dimensions at runtime.</p>
<p>We can retrieve the screen size from the built-in module <code>'Dimensions'</code> using ES6's destructuring assignment syntax:</p>
<pre style="background:#0c1021;color:#f8f8f8"><span style="color:#fbde2d">var</span> {width, height} <span style="color:#fbde2d">=</span> require(<span style="color:#61ce3c">'Dimensions'</span>).get(<span style="color:#61ce3c">'window'</span>);
</pre>
<p>which is a concise way of saying:</p>
<pre style="background:#0c1021;color:#f8f8f8"><span style="color:#fbde2d">var</span> width <span style="color:#fbde2d">=</span> require(<span style="color:#61ce3c">'Dimensions'</span>).get(<span style="color:#61ce3c">'window'</span>).<span style="color:#8da6ce">width</span>;
<span style="color:#fbde2d">var</span> height <span style="color:#fbde2d">=</span> require(<span style="color:#61ce3c">'Dimensions'</span>).get(<span style="color:#61ce3c">'window'</span>).<span style="color:#8da6ce">height</span>;
</pre>
<p>The <code>{width, height}</code> pair is the logical resolution (e.g. 375x667 on iPhone 6) rather than the physical resolution (e.g. 750x1334 on iPhone 6), which, if you need, could be deduced by multiplying the former by the pixel density (<code>require('react-native').PixelRatio.get()</code>).
</p>
<p>On iOS, <code>height</code> represents the full screen height; on Android, however, this value excludes the height of the status bar but includes the system navigational area at the bottom.</p>
<p>The rest is just basic arithmetic. Let's do it in a new component file <code>boardview.js</code>:</p>
<pre style="background:#0c1021;color:#f8f8f8"><span style="color:#61ce3c">'use strict'</span>;
<span style="color:#fbde2d">var</span> React <span style="color:#fbde2d">=</span> require(<span style="color:#61ce3c">'react-native'</span>);
<span style="color:#fbde2d">var</span> {
StyleSheet,
<span style="color:#8da6ce">Text</span>,
View,
} <span style="color:#fbde2d">=</span> React;
<span style="color:#fbde2d">var</span> {width, height} <span style="color:#fbde2d">=</span> require(<span style="color:#61ce3c">'Dimensions'</span>).get(<span style="color:#61ce3c">'window'</span>);
<span style="color:#fbde2d">var</span> SIZE <span style="color:#fbde2d">=</span> <span style="color:#d8fa3c">4</span>; <span style="color:#aeaeae">// four-by-four grid</span>
<span style="color:#fbde2d">var</span> CELL_SIZE <span style="color:#fbde2d">=</span> <span style="color:#8da6ce">Math</span>.<span style="color:#8da6ce">floor</span>(width <span style="color:#fbde2d">*</span> .<span style="color:#d8fa3c">2</span>); <span style="color:#aeaeae">// 20% of the screen width</span>
<span style="color:#fbde2d">var</span> CELL_PADDING <span style="color:#fbde2d">=</span> <span style="color:#8da6ce">Math</span>.<span style="color:#8da6ce">floor</span>(CELL_SIZE <span style="color:#fbde2d">*</span> .<span style="color:#d8fa3c">05</span>); <span style="color:#aeaeae">// 5% of the cell size</span>
<span style="color:#fbde2d">var</span> BORDER_RADIUS <span style="color:#fbde2d">=</span> CELL_PADDING <span style="color:#fbde2d">*</span> <span style="color:#d8fa3c">2</span>;
<span style="color:#fbde2d">var</span> TILE_SIZE <span style="color:#fbde2d">=</span> CELL_SIZE <span style="color:#fbde2d">-</span> CELL_PADDING <span style="color:#fbde2d">*</span> <span style="color:#d8fa3c">2</span>;
<span style="color:#fbde2d">var</span> LETTER_SIZE <span style="color:#fbde2d">=</span> <span style="color:#8da6ce">Math</span>.<span style="color:#8da6ce">floor</span>(TILE_SIZE <span style="color:#fbde2d">*</span> .<span style="color:#d8fa3c">75</span>);
<span style="color:#fbde2d">var</span> BoardView <span style="color:#fbde2d">=</span> React.createClass({
render() {
<span style="color:#fbde2d">return</span> <span style="color:#fbde2d"><</span>View style<span style="color:#fbde2d">=</span>{styles.container}<span style="color:#fbde2d">></span>
{this.renderTiles()}
<span style="color:#fbde2d"><</span>/View<span style="color:#fbde2d">></span>;
},
renderTiles() {
<span style="color:#fbde2d">var</span> result <span style="color:#fbde2d">=</span> [];
<span style="color:#fbde2d">for</span> (<span style="color:#fbde2d">var</span> row <span style="color:#fbde2d">=</span> <span style="color:#d8fa3c">0</span>; row <span style="color:#fbde2d"><</span> SIZE; row<span style="color:#fbde2d">++</span>) {
<span style="color:#fbde2d">for</span> (<span style="color:#fbde2d">var</span> col <span style="color:#fbde2d">=</span> <span style="color:#d8fa3c">0</span>; col <span style="color:#fbde2d"><</span> SIZE; col<span style="color:#fbde2d">++</span>) {
<span style="color:#fbde2d">var</span> key <span style="color:#fbde2d">=</span> row <span style="color:#fbde2d">*</span> SIZE <span style="color:#fbde2d">+</span> col;
<span style="color:#fbde2d">var</span> letter <span style="color:#fbde2d">=</span> <span style="color:#8da6ce">String</span>.<span style="color:#8da6ce">fromCharCode</span>(<span style="color:#d8fa3c">65</span> <span style="color:#fbde2d">+</span> key);
<span style="color:#fbde2d">var</span> position <span style="color:#fbde2d">=</span> {
left: col <span style="color:#fbde2d">*</span> CELL_SIZE <span style="color:#fbde2d">+</span> CELL_PADDING,
top: row <span style="color:#fbde2d">*</span> CELL_SIZE <span style="color:#fbde2d">+</span> CELL_PADDING
};
result.<span style="color:#8da6ce">push</span>(
<span style="color:#fbde2d"><</span>View key<span style="color:#fbde2d">=</span>{key} style<span style="color:#fbde2d">=</span>{[styles.tile, position]}<span style="color:#fbde2d">></span>
<span style="color:#fbde2d"><</span><span style="color:#8da6ce">Text</span> style<span style="color:#fbde2d">=</span>{styles.letter}<span style="color:#fbde2d">></span>{letter}<span style="color:#fbde2d"><</span>/<span style="color:#8da6ce">Text</span><span style="color:#fbde2d">></span>
<span style="color:#fbde2d"><</span>/View<span style="color:#fbde2d">></span>
);
}
}
<span style="color:#fbde2d">return</span> result;
},
});
<span style="color:#fbde2d">var</span> styles <span style="color:#fbde2d">=</span> StyleSheet.create({
container: {
width: CELL_SIZE <span style="color:#fbde2d">*</span> SIZE,
height: CELL_SIZE <span style="color:#fbde2d">*</span> SIZE,
backgroundColor: <span style="color:#61ce3c">'transparent'</span>,
},
tile: {
position: <span style="color:#61ce3c">'absolute'</span>,
width: TILE_SIZE,
height: TILE_SIZE,
borderRadius: BORDER_RADIUS,
justifyContent: <span style="color:#61ce3c">'center'</span>,
alignItems: <span style="color:#61ce3c">'center'</span>,
backgroundColor: <span style="color:#61ce3c">'#BEE1D2'</span>,
},
letter: {
color: <span style="color:#61ce3c">'#333'</span>,
fontSize: LETTER_SIZE,
backgroundColor: <span style="color:#61ce3c">'transparent'</span>,
},
});
module.exports <span style="color:#fbde2d">=</span> BoardView;
</pre>
<p>Notice how we set a unique key property on each tile <code>View</code>. This practice helps React Native to detect virtual DOM changes more efficiently. Whenever you create an array of homogeneous components, remember to do this; otherwise, you'll face a prominent yellow-box warning on the screen.</p>
<p>We are now ready to render this component in the much simplified <code>main.js</code>:</p>
<pre style="background:#0c1021;color:#f8f8f8"><span style="color:#61ce3c">'use strict'</span>;
<span style="color:#fbde2d">var</span> React <span style="color:#fbde2d">=</span> require(<span style="color:#61ce3c">'react-native'</span>);
<span style="color:#fbde2d">var</span> {
StyleSheet,
View,
} <span style="color:#fbde2d">=</span> React;
<span style="color:#fbde2d">var</span> BoardView <span style="color:#fbde2d">=</span> require(<span style="color:#61ce3c">'./boardview.js'</span>);
<span style="color:#fbde2d">var</span> Main <span style="color:#fbde2d">=</span> React.createClass({
render() {
<span style="color:#fbde2d">return</span> <span style="color:#fbde2d"><</span>View style<span style="color:#fbde2d">=</span>{styles.container}<span style="color:#fbde2d">></span>
<span style="color:#fbde2d"><</span>BoardView/<span style="color:#fbde2d">></span>
<span style="color:#fbde2d"><</span>/View<span style="color:#fbde2d">></span>;
},
});
<span style="color:#fbde2d">var</span> styles <span style="color:#fbde2d">=</span> StyleSheet.create({
container: {
flex: <span style="color:#d8fa3c">1</span>,
justifyContent: <span style="color:#61ce3c">'center'</span>,
alignItems: <span style="color:#61ce3c">'center'</span>,
backgroundColor: <span style="color:#61ce3c">'#644B62'</span>,
},
});
module.exports <span style="color:#fbde2d">=</span> Main;
</pre>
<p>Voilà, we've just created a 4x4 grid of letters that scales nicely on both phones and tablets.</p>
<div class="separator" style="clear: both; text-align: center;margin:32px auto;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJHU5X_R_eOZcqaIznl5TiikFFPl6qSlPNuH-8Cxu6HVis_cBSGKdovzCZ3oueXIRsPfMXziZHcnR5cRSJMMeqHitbgwDAgXyyULNIDPl5yc_0YEZpl_1F53NAjvyf5Po5yqyfvP9WWZw/s1600/alpha-20-grid-ios.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJHU5X_R_eOZcqaIznl5TiikFFPl6qSlPNuH-8Cxu6HVis_cBSGKdovzCZ3oueXIRsPfMXziZHcnR5cRSJMMeqHitbgwDAgXyyULNIDPl5yc_0YEZpl_1F53NAjvyf5Po5yqyfvP9WWZw/s375/alpha-20-grid-ios.png" /></a></div>
<h3>6. Custom fonts</h3>
<p>The default system font looks legible yet unadorned; it's also inconsistent across platforms. To improve the typography, we're going to bundle a custom font with the app.</p>
<p>Let's do that for Android first.
<ul>
<li>Step 1: Create a directory <code>android/app/src/main/assets/fonts</code>.</li>
<li>Step 2: Drop .TTF or .OTF fonts there.
<div class="separator" style="clear: both; text-align: center;margin:32px auto"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-SXQPaIFpwzf5cgXW_UKsx07Eg7yv4kJTveppFwar2nzjqRU8DVB_sHCpOCna_uIBtHTx5rPv6YLkaTFJ_J0TVK121cRYISUa7rOpoRnci2YRmx-nSjLiiCZ4-tu9_XYWr0SqjbyvBTA/s1600/alpha-31-android-font.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-SXQPaIFpwzf5cgXW_UKsx07Eg7yv4kJTveppFwar2nzjqRU8DVB_sHCpOCna_uIBtHTx5rPv6YLkaTFJ_J0TVK121cRYISUa7rOpoRnci2YRmx-nSjLiiCZ4-tu9_XYWr0SqjbyvBTA/s480/alpha-31-android-font.png" /></a></div>
The font files will be automatically packaged and ready for use in JavaScript.</li>
<li>Step 3: Reference the custom font name in a <code>fontFamily</code> property in <code>boardview.js</code>.
<pre style="background:#0c1021;color:#f8f8f8"><span style="color:#fbde2d">var</span> styles <span style="color:#fbde2d">=</span> StyleSheet.create({
...
letter: {
fontFamily: <span style="color:#61ce3c">'NukamisoLite'</span>, <span style="color:#aeaeae">// <= custom font name</span>
...
},
});
</pre>
</li>
</ul>
</p>
<p>Now rebuild the Android app by running <code>react-native run-android</code>.</p>
<div class="separator" style="clear: both; text-align: center;margin:32px auto"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXL-L4Njr4mbzjvQhcHDxdyXegmfUTGNBzeGjT7IrTABQ2C6FO3TOegItt5XJ7o_d5bZfliNpf4oJ6PGMaUwpOIY-5tdWxq7wL1xpJhhFdxJTysggKQ3akP6SB4L8zNDK68JYKNCfGGwc/s1600/alpha-32-android-font.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXL-L4Njr4mbzjvQhcHDxdyXegmfUTGNBzeGjT7IrTABQ2C6FO3TOegItt5XJ7o_d5bZfliNpf4oJ6PGMaUwpOIY-5tdWxq7wL1xpJhhFdxJTysggKQ3akP6SB4L8zNDK68JYKNCfGGwc/s520/alpha-32-android-font.png" /></a></div>
<p>Let's move on to iOS.
<ul>
<li>Step 1: Drag and drop custom font files into the Project Navigator in XCode.
<div class="separator" style="clear: both; text-align: center;margin:32px auto;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqd4JjjjZ580jNNQErO_mXlHt7tscB46S11OfdlMu0NSW8NOxSuHVhDSeo1xhDYr3FTCqsMUniAJMujwhgJG4RFuDV2xB4FYoZ35TJdVcTKHTO8zVjbq2fvN1nf1AaXLPxb4FiNzrpZ5Q/s1600/alpha-33-ios-font.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqd4JjjjZ580jNNQErO_mXlHt7tscB46S11OfdlMu0NSW8NOxSuHVhDSeo1xhDYr3FTCqsMUniAJMujwhgJG4RFuDV2xB4FYoZ35TJdVcTKHTO8zVjbq2fvN1nf1AaXLPxb4FiNzrpZ5Q/s1600/alpha-33-ios-font.png" /></a></div>
When prompted, make sure the fonts are added to the primary build target.
<div class="separator" style="clear: both; text-align: center;margin:32px auto"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3iuiJKeP9hQ2BkesjJ5v9WW7VVj0HTmOs6RT1AWO0O38zmpljr4kLBNHjfW53L_zPAlxYY_7ZDMzh0y-UcGMeyMe_eFo1elbaT37zpNKhWHCcZxQGUmhrCyZsp-2hxQKit3_7uCn00Co/s1600/alpha-34-ios-font.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3iuiJKeP9hQ2BkesjJ5v9WW7VVj0HTmOs6RT1AWO0O38zmpljr4kLBNHjfW53L_zPAlxYY_7ZDMzh0y-UcGMeyMe_eFo1elbaT37zpNKhWHCcZxQGUmhrCyZsp-2hxQKit3_7uCn00Co/s520/alpha-34-ios-font.png" /></a></div>
</li>
<li>Step 2: Select <code>Info.plist</code> in the Project Navigator. Click the "+" button next to "<code>Information Property List</code>" to add a new row. Find "<code>Fonts provided by application</code>" from the drop-down list.
<div class="separator" style="clear: both; text-align: center;margin:32px auto"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXqVX17lRr0oLP4hvqfvTnkxqN10QafAhX8_YNBSXUKnzI3T4R4Qao_mPXDWapTm2O-snJzdnN6NSzL5OGclA903wYHf-u_IwQMVf45ybN0kgDknHKZt5ir2wYEdFOa6GEF9WkTQ4gW7g/s1600/alpha-35-ios-font.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXqVX17lRr0oLP4hvqfvTnkxqN10QafAhX8_YNBSXUKnzI3T4R4Qao_mPXDWapTm2O-snJzdnN6NSzL5OGclA903wYHf-u_IwQMVf45ybN0kgDknHKZt5ir2wYEdFOa6GEF9WkTQ4gW7g/s520/alpha-35-ios-font.png" /></a></div>
</li>
<li>Step 3: Expand the new row by clicking the triangular icon. Double-click the empty Value slot and enter the font file name without its full path.
<div class="separator" style="clear: both; text-align: center;margin:32px auto"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEie3p9I4ugT0ubcizpDULvD_rcjRUpW22EEKIBcM6cORSspLuC57UClyE5-4fCFtVjYMbt2IVur4urvJvad7Qxno5xxGQV_5syTPblCZ_3iMg9gOIS1DqzqghKr6XUmtmIrHJXiNI9DVow/s1600/alpha-36-ios-font.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEie3p9I4ugT0ubcizpDULvD_rcjRUpW22EEKIBcM6cORSspLuC57UClyE5-4fCFtVjYMbt2IVur4urvJvad7Qxno5xxGQV_5syTPblCZ_3iMg9gOIS1DqzqghKr6XUmtmIrHJXiNI9DVow/s520/alpha-36-ios-font.png" /></a></div>
You may click the "+" button next to "Item 0" to add additional fonts.
</li>
</ul>
</p>
<p>Since we're using the same code base for both platforms, there's no need to edit JavaScript if you've already done step 3 for Android. Just rebuild the iOS app and check the results. (We've added a new asset to the project, so we have to rebuild rather than Cmd+R refresh.)
<div class="separator" style="clear: both; text-align: center;margin:32px auto"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXsIrY9Eo4MuYSdkfIwGLV1si78xJ0TJOFPnU1__1PkKSMa88PTLJKw-LN7kGf-1Hub8s6kl9hLRHuG0qmeEzTTsG1rZmye9enewow9un13dRUdvKus_QWf4xon8yPYdAHLtmUNAAQYEw/s1600/alpha-37-ios-font.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXsIrY9Eo4MuYSdkfIwGLV1si78xJ0TJOFPnU1__1PkKSMa88PTLJKw-LN7kGf-1Hub8s6kl9hLRHuG0qmeEzTTsG1rZmye9enewow9un13dRUdvKus_QWf4xon8yPYdAHLtmUNAAQYEw/s520/alpha-37-ios-font.png" /></a></div>
</p>
<p>Full source of this part of the tutorial can be downloaded from <a href="https://github.com/zmxv/alpha-reflex/releases/tag/v0.1">https://github.com/zmxv/alpha-reflex/releases/tag/v0.1</a>. The next article will discuss high-performance animations, touch event handling, and more.</p>
<p>(<a href="http://blog.zmxv.com/2016/01/lets-write-a-mobile-game-with-react-native-part-2.html">Part 2</a>: touch event handling and property animation)</p>Unknownnoreply@blogger.com8tag:blogger.com,1999:blog-9100824746800598084.post-11677070413904394872016-01-08T23:30:00.000-08:002016-01-09T00:42:44.880-08:00Alpha Reflex<p>During the week of Christmas, I built a simple mobile game <i>Alpha Reflex</i> using React Native — yeah, React Native is a peculiar choice for game development; I'll explain the why and how in another post. One week after I submitted it for Apple's app review, the game is finally live on <a href="https://itunes.apple.com/app/id1071591514">App Store</a> today! It's also available for Android on <a href="https://play.google.com/store/apps/details?id=com.pretonic.alphareflex">Play Store</a>.</p>
<p style="text-align:center"><a href="https://itunes.apple.com/app/id1071591514" title="Download Alpha Reflex for iOS"><img src="http://www.pretonic.com/img/badge-appstore-en-50.png" style="border:0;padding:0;margin:0 8px;"/></a><a href="https://play.google.com/store/apps/details?id=com.pretonic.alphareflex" title="Download Alpha Reflex for Android"><img src="http://www.pretonic.com/img/badge-gplay-en-50.png" style="border:0;padding:0;margin:0 8px;"/></a></p>
<p>Alpha Reflex challenges you to find letters in alphabetical order as fast as possible. It's designed to train your reflexes and enhance your peripheral vision. It also helps me memorize the Russian alphabet which I learned while debugging the game.</p>
<div class="separator" style="clear: both; text-align: center;margin:32px auto"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimatVSFzPPOv-uZuLTfYfoRubhhC7tkzq4Wop2jX12_NR8aTfMsho4YQOfPA3G0qIBnflktH8ZxZyT_WjxIb9R2HGF8UdGdtUCB9KDrXNyh0fKL3-n61_2DtfLhl0rHEJFBFiawQdC_Tk/s1600/promo-1024x500.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimatVSFzPPOv-uZuLTfYfoRubhhC7tkzq4Wop2jX12_NR8aTfMsho4YQOfPA3G0qIBnflktH8ZxZyT_WjxIb9R2HGF8UdGdtUCB9KDrXNyh0fKL3-n61_2DtfLhl0rHEJFBFiawQdC_Tk/s512/promo-1024x500.png" /></a></div>
<p>My personal record so far is 180 APM (8.66 seconds for the English alphabet); by my estimation, the human limit should be around 480 APM (i.e. 3.0 seconds for Greek, 3.3 seconds for English, 4.1 seconds for Russian, and 5.8 seconds for Japanese.)</p>
<p>One thing still puzzles me: I used the same code base and vector assets for iOS and Android, but the iOS app size is 1.9MB while the Android release build ends up to be a whopping 8.9MB. The lion's share of the bloat comes from a 5.7MB classes.dex file (uncompressed size). I have no idea why it is so or how to shrink the package.</p>
<p><b>Update</b>: after digging into the AAR directories, I think I know why — React Native bundles the JSC library into the Android APK. The shared object file libjsc.so for ARMv7-A is 1.9MB uncompressed, and the x86 version 4.6MB. On iOS, I believe React Native leverages the shared JavaScriptCore engine provided by the OS, so the IPA package is much smaller. It's still unclear to me how to significantly trim the app size on Android. Maybe it hinges on React Native switching to the V8 engine?</p>
Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-9100824746800598084.post-31532329380603653662015-12-23T23:07:00.000-08:002015-12-23T23:07:01.450-08:00Cross-platform release of react-native-sound<p>I've added support for Android in the latest release of <a href="https://github.com/zmxv/react-native-sound">react-native-sound</a>, a native module to play sound clips in React Native apps. Most features have been ported as shown in the following table:</p>
<p>
<table style="border-collapse: collapse; border-spacing: 0px; box-sizing: border-box; color: #333333; display: block; word-break: keep-all;"><thead style="box-sizing: border-box;">
<tr style="background-color: white; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-top-width: 1px; box-sizing: border-box;"><th style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">Feature</th><th style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">iOS</th><th style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">Android</th></tr>
</thead><tbody style="box-sizing: border-box;">
<tr style="background-color: white; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-top-width: 1px; box-sizing: border-box;"><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">Load sound from the app bundle</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td></tr>
<tr style="background-color: #f8f8f8; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-top-width: 1px; box-sizing: border-box;"><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">Load sound from other directories</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;"></td></tr>
<tr style="background-color: white; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-top-width: 1px; box-sizing: border-box;"><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">Play sound</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td></tr>
<tr style="background-color: #f8f8f8; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-top-width: 1px; box-sizing: border-box;"><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">Playback completion callback</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td></tr>
<tr style="background-color: white; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-top-width: 1px; box-sizing: border-box;"><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">Pause</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td></tr>
<tr style="background-color: #f8f8f8; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-top-width: 1px; box-sizing: border-box;"><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">Resume</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td></tr>
<tr style="background-color: white; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-top-width: 1px; box-sizing: border-box;"><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">Stop</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td></tr>
<tr style="background-color: #f8f8f8; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-top-width: 1px; box-sizing: border-box;"><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">Release resource</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td></tr>
<tr style="background-color: white; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-top-width: 1px; box-sizing: border-box;"><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">Get duration</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td></tr>
<tr style="background-color: #f8f8f8; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-top-width: 1px; box-sizing: border-box;"><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">Get number of channels</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;"></td></tr>
<tr style="background-color: white; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-top-width: 1px; box-sizing: border-box;"><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">Get/set volume</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td></tr>
<tr style="background-color: #f8f8f8; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-top-width: 1px; box-sizing: border-box;"><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">Get/set pan</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;"></td></tr>
<tr style="background-color: white; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-top-width: 1px; box-sizing: border-box;"><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">Get/set loops</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td></tr>
<tr style="background-color: #f8f8f8; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-top-width: 1px; box-sizing: border-box;"><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">Get/set current time</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td><td style="border: 1px solid rgb(221, 221, 221); box-sizing: border-box; padding: 6px 13px;">✓</td></tr>
</tbody></table>
</p>
<p>I think <a href="https://github.com/zmxv/react-native-sound">react-native-sound</a> is so far the most feature-rich open-source module for audio playback in React Native apps :)</p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-9100824746800598084.post-81738368657783838662015-12-03T13:56:00.000-08:002015-12-03T13:56:18.300-08:00Major update of the react-native-sound moduleI've published a major update of the <a href="https://github.com/zmxv/react-native-sound">react-native-sound</a> native module for iOS. It now enables easy control of sound volume, pan position, loops, playback time, completion callback. It can also load sound files from directories other than the main bundle. To implement these features, I had to make some backward-incompatible changes to the JavaScript API. Please check out the new example and API doc before you upgrade.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-9100824746800598084.post-78839311099844087372015-11-28T13:32:00.000-08:002015-11-28T13:32:15.571-08:00React Native Tip: TouchableOpacity supports multiple childrenReact Native's <code><a href="http://facebook.github.io/react-native/docs/touchableopacity.html">TouchableOpacity</a></code> used to require a single child element. To attach a touch event handler to multiple components, you had to first wrap them inside a container <code><View></code>:
<pre style="background:#000000;color:#e0eee0;padding:1em;font-size:small"><font face="monospace"><font color="#7c7268">// Old way</font>
<TouchableOpacity>
<View>
<Image ... />
<Text>...</Text>
</View>
</TouchableOpacity>
</font></pre>
It wasn't documented anywhere, but one bonus effect of <a href="https://github.com/facebook/react-native/commit/725053acfeba4c7f2a21ac47ae8100588a710476">commit 725053a</a> is that <code>TouchableOpacity</code> now has an <code>Animated.View</code> root which supports multiple children. So if you've upgraded to React Native 0.14.0 or higher, you can now get rid of the container view when using <code>TouchableOpacity</code>:
<pre style="background:#000000;color:#e0eee0;padding:1em;font-size:small"><font face="monospace"><font color="#7c7268">// New way</font>
<TouchableOpacity>
<Image ... />
<Text>...</Text>
</TouchableOpacity>
</font></pre>
<code>TouchableHighlight</code> and <code>TouchableWithoutFeedback</code> still require a single child as of RN 0.16.0, though I would normally avoid using them anyway — <code>TouchableHighlight</code> often creates undesirable artifacts, and <code>TouchableWithoutFeedback</code> leads to a poor user experience when used alone.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-9100824746800598084.post-44602636504937264342015-10-19T16:56:00.000-07:002015-12-04T11:49:00.189-08:00Playing sound in React Native apps<p><b>Update:</b> I've made significant improvements to the module. Please refer to the <a href="https://github.com/zmxv/react-native-sound/blob/master/README.md">documentation</a> for the latest APIs.</p>
<hr/>
<p>I've open-sourced '<a href="https://github.com/zmxv/react-native-sound">react-native-sound</a>', a native module for playing sound files in React Native apps. It supports preloading and sound mixing with an audio player pool. The module is currently iOS-only, but an Android port is coming soon.</p>
<h3>Installation</h3>
<pre style="background:#000000;color:#e0eee0;padding:1em;font-size:small"><font face="monospace">npm install react-native-sound --save</font></pre>
<p>In XCode, right click <b>Libraries</b>. Click <b>Add Files to "[Your project]"</b>. Navigate to <b>node_modules/react-native-sound</b>. Add the file <b>RNSound.xcodeproj</b>.
In the project navigator, select your project. Click the build target. Click <b>Build Phases</b>. Expand <b>Link Binary With Libraries</b>. Click the plus button and add <b>libRNSound.a</b> under Workspace.
</p>
<h3>Usage example</h3>
<p>Now you're ready to play sound clips in your React Native app. Just drop sound files in your XCode project and call the following APIs from JavaScript.</p>
<pre style="background:#000000;color:#e0eee0;padding:1em;font-size:small"><font face="monospace"><font color="#8db6cd">var</font> Sound = require(<span style="background-color: #000000"><font color="#80a0ff">'react-native-sound'</font></span>);<br>Sound.enable(<span style="background-color: #000000"><font color="#00ffff"><b>true</b></font></span>); <font color="#7c7268">// Enable sound</font><br>Sound.prepare(<span style="background-color: #000000"><font color="#80a0ff">'tap.aac'</font></span>); <font color="#7c7268">// Preload the sound file 'tap.aac'</font><br>Sound.play(<span style="background-color: #000000"><font color="#80a0ff">'tap.aac'</font></span>); <font color="#7c7268">// Play the sound 'tap.aac'</font></font></pre>
<h3>Notes</h3>
<ul>
<li>Sound.enable(true) must be called before playing any sound.</li>
<li>Sound.prepare(...) preloads a sound file and prepares it for playback. If you do not call Sound.prepare(...) beforehand, Sound.play(...) will still work, but there might be a noticeable delay on the first call.</li>
<li>You can make multiple Sound.play(...) calls at the same time. Under the hood, this module uses AVAudioSessionCategoryAmbient to mix sounds.</li>
<li>The module wraps AVAudioPlayer which supports aac, aiff, mp3, wav etc. The full list of supported formats can be found at <a href="https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/MultimediaPG/UsingAudio/UsingAudio.html">https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/MultimediaPG/UsingAudio/UsingAudio.html</a></li>
</ul>Unknownnoreply@blogger.com4tag:blogger.com,1999:blog-9100824746800598084.post-84412966937855738642015-09-15T01:02:00.000-07:002016-01-09T12:00:26.262-08:00What I learned from building a React Native app<a href="https://facebook.github.io/react-native/">React Native</a> is Facebook's open-source framework for building native mobile apps using JavaScript. Unlike PhoneGap/Cordova, React Native provides bindings for native UI controls which totally outclass HTML-based hybrid solutions. After playing with the sample app for a while, I decided to jettison my Cordova codebase and rewrite the <a href="http://www.circadi.com/">Circadi</a> app using this new framework. It took me about 96 working hours to finish an MVP, now published on the <a href="https://itunes.apple.com/us/app/circadi/id1035015954?ls=1&mt=8">App Store</a>. My overall experience with React Native is positive. I'm going to give an account of my key findings below for people who're considering to adopt the framework.<br />
<br />
<h3>Advantages</h3><br />
<h4>Developer productivity</h4><br />
React Native enables developers to write native apps in JavaScript with ECMAScript 5/6 features and optional type checking using <a href="http://flowtype.org/">Flow</a>. This alone is a huge boon to my productivity even if cross-platform code reuse were never promised. Having written Objective-C and Java at my former employer, I much prefer writing apps in a more <a href="http://blog.zmxv.com/2015/07/code-golf-at-google.html">terse</a> language like JavaScript despite its many foibles.<br />
<br />
The interpretive nature of JavaScript also significantly shortens the RN app dev cycle, making it as easy as developing web pages. Reloading a RN app in a second by pressing ⌘+R turns out to be a truly <i>refreshing</i> experience. (You do need to recompile the XCode project when you switch between build targets, link in a new native module, add a resource file, patch an Objective-C implementation, or resolve the occasional red-screen-of-death.)<br />
<br />
Another design choice that facilitates quick UI iteration is React Native's <a href="https://facebook.github.io/react-native/docs/flexbox.html">flexbox</a> and <a href="https://facebook.github.io/react-native/docs/style.html">styling abstraction</a>, modeled after a carefully chosen subset of CSS features. Layout tweaking using the flexbox model rarely gives me any unpleasant surprises, unlike my traumatic experience grappling with Interface Builder's constraint editor.<br />
<br />
<h4>App performance</h4><br />
React Native app's UI components feel native because they <i>are</i> native controls exposed to JavaScript via a JS/Obj-C bridge. Achieving the same level of look-and-feel in HTML is a sisyphean task — eventually you'll have to either compromise on fidelity or resort to a non-standard UI theme.<br />
<br />
React Native borrows the <a href="https://facebook.github.io/react/docs/dom-differences.html">virtual DOM diff</a> idea from React and makes it conceptually simple to write high-performance UI. If you try to repaint a full-screen background 60 times per second, RN will stutter; but for most non-game apps with few moving parts, it's not hard to create a smooth experience.<br />
<br />
<h3>Caveats</h3><br />
<h4>Cross-platform support</h4><br />
Facebook finally unveiled <a href="https://code.facebook.com/posts/1189117404435352/react-native-for-android-how-we-built-the-first-cross-platform-react-native-app/">React Native for Android</a> yesterday, almost six months after the iOS release. However, porting my RN app to Android is non-trivial. First of all, some of the UI controls such as SwitchIOS need to be replaced with Android counterparts; some (e.g. ActionSheetIOS) must be scratched and redesigned for the targeting platform. A more pressing issue is porting all of the native libraries my app depends on. I'll end up writing Java extensions or waiting for others to do it.<br />
<br />
There's also no obvious way to write Apple Watch apps in RN yet.<br />
<br />
<h4>Library support</h4><br />
React Native has fewer than 30 built-in components so far, iOS and Android combined. Chances are good that you'll need to find (or write your own) extensions to implement some basic features, for example, accessing SQLite databases or supporting in-app-purchase items. You'll be hurled back to the Dark Ages of app development, so be prepared with some knowledge of Objective-C and Java. Fluency in native code will be useful anyway even if you don't write RN extensions because: (1) React Native's documentation isn't on par with Apple's yet. You'll sometimes need to dive into the .m files to ferret out implementation details. (2) React Native is still a young framework, fraught with <a href="https://github.com/facebook/react-native/issues">bugs</a> on the other side across the JS bridge.<br />
<br />
<h4>ES6 language support</h4><br />
Although you can use ECMAScript 6 features in your RN app, be aware that when you run <code>react-native bundle --minify</code> for release build, RN transpiles JavaScript into ES5 using Babel before calling <a href="https://github.com/mishoo/UglifyJS2">UglifyJS2</a> for minification. However, not all ES6/7 syntax get <a href="http://facebook.github.io/react-native/docs/javascript-environment.html#javascript-syntax-transformers">transformed</a>, and UglifyJS will <a href="https://github.com/mishoo/UglifyJS2/issues/448">choke</a> on statements like <code>for (... of ...)</code>, <code>yield</code>, etc. This is not technically a RN issue, but it will bite you during the release phase if you go too fancy with ES6 goodies.<br />
<br />
<h3>Conclusion</h3><br />
I find React Native a good fit for JavaScript programmers to develop decent native apps with mostly standard UI. This fledgling framework has shown great promise to reduce app development cost, posing a real threat to Cordova and Appcelerator's Titanium.
<p><b>Update</b>: I <a href="/2016/01/alpha-reflex.html">published a game</a> using React Native.</p>Unknownnoreply@blogger.com7tag:blogger.com,1999:blog-9100824746800598084.post-54537913015331855832015-09-02T11:14:00.000-07:002015-09-02T11:14:13.155-07:00Crowd's reaction to Google's new logoShortly after announcing a company restructuring plan, Google <a href="http://googleblog.blogspot.com/2015/09/google-update.html">unveiled a new logo</a> yesterday. Do people like the sans-serif design or still prefer the classic logo? I set out to ask the question on three popular crowdsourcing platforms: <a href="https://www.mturk.com/">Amazon Mechanical Turk</a> (MT), <a href="https://www.crowdflower.com/">CrowdFlower</a> (CF), and <a href="http://www.google.com/insights/consumersurveys/">Google Consumer Survey</a> (GCS). My survey displays the new and the classic logo and asks participants to pick the one they like more. They may optionally write down a line to explain their choice.<br />
<br />
<img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinz1CAhBtGMKBArKiV9Mmr_PAQ_iLsUS_uq3orwfEsodSAx-c2uQFqfWNM9_ipi3ITE_psN_9Pt1JlnmipNkE1-Dbgk89N95XjA7ynSFByolzkHXGWwtHIUQ_UDiHq8eN_5-vG-6AC2ig/s1600/opinion-rewards-screenshot.png" /><br />
<br />
Over the past few hours, 874 valid responses have been collected. Across all three platforms, there's a predominance of preference for the classic Google logo.<br />
<br />
<img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuaQCOQMGvxgtcTMQ9155xoWxSOcIyfR85YqblZElSwG3YQzgTpL3Ip_qBWjTy1tgq-u4RYX0Rt17utZhh0XEWG298GvQaB_bKULcFNEW2oHj4fLgGzcxB4a6D7aREmUrtp2ICTOA1JI8/s1600/image+%25285%2529.png" /><br />
<br />
<img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEij9Lmg1Ol346bO0afP7FI3w9AxtewkLC0g8lHDF0eDiR4tNVC8GWdXlcr_X27dqFLpTNU6n_wl84dm_OtPHvFjtc4KZ6kn1xJAjQNzhrso0et4SgWK45U8Wo_BBfhyphenhyphenSr8vuW0NFjv4ArY/s1600/image+%25282%2529.png" /><br />
<br />
<img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLMbOli7Q05DdOgSoAUyaP49oQaXQBroXSC1ihgm3ijtXkptKUbe8h8ITSUXu_ge3aEU1zTcyKDJ-D3UA1LFdzY9ZbDghUUKJZAZp2WaqdSiuzl0q2MODQQcxKovr0kmHWaY7iqLXdKCY/s1600/image+%25283%2529.png" /><br />
<br />
<img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqIico8AKOunkJxiuzMO8SrsTO9lZb7NmkjOU60fLm_8m3h16_eJ2Ov75PgdVTYIVrJjpagllZ8lIieMkFeeommLjnT5dDTgmXTQAXkNSQwHE-Y9f3BNMN_yho7iOpSVSc-e1J2XDR0b4/s1600/image+%25286%2529.png" /><br />
<br />
All GCS participants come from the US while 99% of the CF crowd are from other countries. The demographics of participants on MT are unknown, but 80% of the overall workforce there are US-based according to <a href="demographics.mturk-tracker.com/#/countries/all">mturk-tracker</a>.<br />
<br />
<img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQFKr6KrbZRxpeZAuk_QWhBTIcLdi9p09f_8g5sgoGUOB-yRMQ55fQV4oJhQH_8dPs1AhUrP6IS7irmxFN-SAcqlRHvTywQnDSaBYm3JMdi1XlBKeFh6GPiK5SkcL4CdkO1u2r_o9LxHE/s1600/confident-in-winner.png" /><br />
<br />
<br />
Although it's optional to provide a reason, many people did opt to write one. Their descriptions of the classic logo are universally positive while reception of the new logo seems mixed.<br />
<br />
Common words that describe the two logos:<br />
<br />
<table border="1"><tr>
<th>New logo</th>
</tr>
<tr><td><span style="color:green">cute</span>, <span style="color:green">clean</span>, <span style="color:green">fun</span>, <span style="color:green">playful</span>, <span style="color:green">friendly</span>, <span style="color:green">casual</span>, <span style="color:green">fresh</span>, <span style="color:green">modern</span>, <span style="color:green">whimsical</span>, <span style="color:green">minimalist</span>, <span style="color:green">bold</span>, <span style="color:red">childish</span>, <span style="color:red">juvenile</span>, <span style="color:red">bland</span>, <span style="color:red">plain</span>, <span style="color:red">awkward</span>, <span style="color:red">boring</span>, <br />
<span style="color:red">clunky</span>, <span style="color:red">dull</span>, <span style="color:red">bubbly</span>, <span style="color:red">cheap</span>, <span style="color:red">ugly</span><br />
</td></tr>
<tr>
<th>Classic logo</th>
<tr><td><span style="color:green">sophisticated</span>, <span style="color:green">professional</span>, <span style="color:green">recognizable</span>, <span style="color:green">trustworthy</span>, <span style="color:green">credible</span>, <span style="color:green">authentic</span>, <span style="color:green">elegant</span>, <span style="color:green">classy</span>, <span style="color:green">fluid</span>, <span style="color:green">sleek</span>, <span style="color:green">pleasing</span>, <span style="color:green">familiar</span>, <span style="color:green">stylish</span>, <span style="color:green">sharp</span>, <span style="color:green">iconic</span>, <span style="color:green">attractive</span>, <span style="color:green">striking</span>, <span style="color:green">mature</span>, <span style="color:green">cool</span><br />
</td></tr>
</tr>
</table><br />
It's probably too early to call Google's new logo a branding failure. The general public's initial negative reaction might be attributed to change aversion. I recognize that, for example, my personal bias against the new logo is not just an aesthetic judgment but also a result of an emotional attachment to the classic Catull font. Many of my Googler friends share the same sentiment, but some of them reported that after a week-long preview period, they hated it less. Maybe the sans-serif new logo will eventually grow on us as the company evolves.Unknownnoreply@blogger.com8tag:blogger.com,1999:blog-9100824746800598084.post-11693966395477719312015-07-22T10:38:00.000-07:002015-07-22T10:42:06.434-07:00New Project: CircadiAs mentioned in <a href="http://www.itworld.com/author/Phil-Johnson/">Phil Johnson</a>'s latest ITWorld story on <a href="http://www.itworld.com/article/2950703/careers/fore-how-code-golf-became-a-thing-at-google.html">code golf at Google</a>, I'm now building a free mobile app called <a href="http://www.circadi.com/">Circadi</a> that aims to help people live a more conscious, fulfilling life. I designed Circadi to scratch my own itch, but if you're interested in the quantified-self movement or are using wearable devices to track biometrics, you might want to leave your email at <a href="http://www.circadi.com/">circadi.com</a> to be an early adopter.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-9100824746800598084.post-31381316644500244922015-07-10T20:14:00.000-07:002015-07-22T10:40:39.990-07:00Code Golf at Google<p>Update: Phil Johnson from ITWorld wrote an <a href="http://www.itworld.com/article/2950703/careers/fore-how-code-golf-became-a-thing-at-google.html">article</a> on this.</p><br />
<h3>What is code golf</h3><p>Code golf is a type of programming competition where contestants strive to write the shortest code that solves a specific problem. Solutions are solely ranked by their code size as long as they pass all tests within certain time and space limits. This mostly sedentary recreational activity has nothing to do with swinging clubs on golf links other than the similarity between the two scoring systems, where lower is better.</p><p>Excelling at conventional programming competitions (e.g. ACM ICPC, Code Jam, or TopCoder) demands mastery of algorithms and speed of coding. Code golf, however, celebrates a different skill set. Expert golfers exhibit not only algorithmic prowess but also a deep knowledge of arcane language features.</p><br />
<h3>Code golf at Google</h3><p>During a two-week leave from Google in June 2014, I was reflecting on the convoluted Java frameworks widely adopted at work. Those hefty frameworks brought coding structures and conventions to large engineering teams; meanwhile, they also sucked the fun of programming like a Pastafarian monster slurping all the tomato sauce on a plate of spaghetti.</p><p>To bring back the fun while fostering a culture that hails code brevity, I decided to start a 20% project to create an internal code golf platform — a playground for Google engineers to show off wits, a fountain of programming tricks for the studious, and a symbol of defiance against the trend of technical complexity permeating through the company. Later in the development, it occurred to me that the system could also be purposed as a recruiting tool: interviewers would benefit from a growing repertoire of coding questions with high-coverage test cases against which candidates's code can be automatically validated. And that became the ostensible mission statement on the code golf site to justify the business case ;)</p><br />
<h3>Design choices</h3><p>The site I designed (<a href="http://go/codegolf">http://go/codegolf</a> if you're a Googler) differentiates itself in two major aspects from public code golf attractions such as <a href="http://codegolf.stackexchange.com/">http://codegolf.stackexchange.com/</a> and <a href="http://golf.shinh.org/">http://golf.shinh.org/</a>:<br />
<dl><dt>Ease-of-use</dt>
<dd><p>Each programming puzzle asks you to write a single function rather than a full program. This eliminates the need to write any boilerplate I/O logic. It's pretty easy to write code directly in the browser which displays instant test results, but if you prefer offline testing, all test cases used by the online judge are conveniently available in JSON.</p><p>As of July 2015, the site supports automated testing of code in JavaScript, Go, Python, C++, and Haskell. Depending on the language you choose, the corresponding function signature is automatically formatted for you. Once your solution passes all the tests, it instantly gets ranked on the leaderboard against other entries in the same language category based on the total byte size.</p></dd>
<dt>Competitiveness</dt>
<dd><p>The site runs a new contest every week. Scores and authors of the top solutions are always shown on the leaderboard, but the source code won't be revealed until the contest is over — at which point, challengers are allowed to submit even shorter code, though that won't affect the finalized ranking. To encourage early submissions, time is used as a tie-breaker. At the end of a contest, everyone who has solved a problem receives a performance score based on their ranking, relative code size, and participation rate of the contest. The very top contestants also get special badges of honor.</p></dd> </dl></p><br />
<h3>Tech stack</h3><p>The code golf site runs on Google App Engine and Compute Engine. It's a concoction of a frontend server in Go and JavaScript plus a collection of backend sandboxes. The biggest challenge was building those sandboxes to execute untrusted code in a controlled environment. Another tricky part was modeling the data to enable problem writing in a language-agnostic way.</p><br />
<h3>Code golf community at Google</h3><p>Since its launch in mid-June 2014, the code golf site has garnered a growing number of loyal users. Hundreds of Googlers have participated in the weekly contests. Some of them confessed in private that they had developed an unhealthy addiction that negatively impacted their productivity, to which I remedied by refraining from offering any monetary rewards to code golf winners ;)</p><p>Code golf tournaments have covered a diverse range of challenges from deceptively trivial CS101 exercises to number theory riddles. The themes also vary greatly, including tributes to acts of Googliness, travesty of the performance review system, and taunts on unpopular company decisions.</p><p>What fascinated me most was the extraordinary quality of winning entries. Sometimes I would run a contest that's identical to one on codegolf.stackexchange.com. The top voted entries on Stack Exchange are just not on par with the best solutions I've witnessed at Google, which exude an elevated level of creativity and craftsmanship rarely found elsewhere. Reading the code can easily trigger a software engineer's imposter syndrome, a profound feeling of humbleness, tinged with a sense of proudness to be working with these talented people.</p><p>For example, in real world scenarios, I've never encountered a single use case of division by zero; but at code golf, a former mathematics professor once won a JavaScript challenge by employing an ingenious "m/=0" expression to reduce code size. He later wrote a 1200-word monograph to dissect his winning entry and shed light on previous iterations. From his article, I also learned that in JavaScript, calling Math.min() with zero arguments returns infinity — an interesting tidbit that may never be useful.<br />
</p><p>I feel obliged to share some outstanding one-liner solutions for a classic programming problem below: Given a list of strings representing a grid of cells ("0"s for dead cells, "1"s for live cells), write a function g() that returns the next iteration following the rules of <a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life">Conway's Game of Life</a>. For example, given the input ["111", "110", "100"], the expected output is ["101", "001", "110"].</p><dl><dt>JavaScript (114B)</dt>
<dd><code>function g(b,y,e){for(j=s='';q=e&&b[j++]*16+8;s+=q&1)for(x=9;x--;q>>=(10+e[y-x%3+1])[j+x/3|0]);return s||b.map(g)}</code></dd>
<dt>Go (118B)</dt>
<dd><code>func g(a[]string)(r[]string){for i,p:=range a{r=append(r,"");for j,q:=range p{x:=1/-^i*3;q^=18;for;x/3<3-i/^-len(a);x++{q<<=("0"+a[i+x/3-1]+"0")[j+x%3]%2};r[i]+="01"[q/16&1:][:1]}};return}</code></dd>
<dt>Python (123B)</dt>
<dd><code>g=lambda a,e=enumerate:[`[+(2<`zip(*a[i+i/~i:i+2])[j+j/~j:j+2]`.count('1')<4+int(c))for j,c in e(x)]`[1::3]for i,x in e(a)]</code></dd>
<dt>C++ (138B)</dt>
<dd><code>template<class V>V g(V a){V b=a;for(r:b)for(p:r){p^=50;for(R:a)for(P:R)p<<=&R-&a[&r-&b[1]]<3u&3u>&p-&r[&P-&R[1]]&P;p=48+p/16%2;}return b;}</code></dd>
<dt>Haskell (104B)</dt>
<dd><code>z=zip[2..] f i=drop(i-3).take i g a=[[("0001"++c:"0000")!!mod(read$f i a>>=f j)9|(j,c)<-z r]|(i,r)<-z a]</code></dd> </dl><p>(If you're a Googler, check out the full contest results and discussions at <a href="http://go/codegolf/11">http://go/codegolf/11</a>)</p><p>I was the sole maintainer of the code golf site for a while. Over time, more contributors volunteered to design problems, review tests, write editorials, implement features, and eventually take over the site ownership when I left Google. It's a bittersweet feeling to leave my brainchild to more capable hands. I don't really feel attached to any code I wrote, but the amazing code golf subculture at Google and the friendships developed within the community will always have a special place in my heart.</p>Unknownnoreply@blogger.com13tag:blogger.com,1999:blog-9100824746800598084.post-78104735072230516572015-05-23T15:47:00.000-07:002015-05-23T15:47:59.000-07:00Custom Bazel build rules to compile TypeScriptI've written a Skylark module for <a href="http://bazel.io/">Bazel</a> to compile <a href="http://www.typescriptlang.org/">TypeScript</a> projects. Source code and example build files are available at <a href="https://github.com/zmxv/bazel-custom-rules">https://github.com/zmxv/bazel-custom-rules</a>.<br />
<br />
Two new rules are introduced by typescript.bzl: <b>ts_library</b> (to group TypeScript modules) and <b>ts_binary</b> (to compile TypeScript sources into a single JavaScript file). Additional compiler options may be passed to <b>tsc</b> via the optional <i>flags</i> attribute as shown below.<br />
<style type="text/css">
<!--
pre { white-space: pre-wrap; font-family: monospace; color: #e0eee0; background-color: #000000; padding: 8px; font-size: 11px;}
.String { color: #80a0ff; background-color: #000000; padding-bottom: 1px; }
-->
</style><br />
<pre>ts_library(
name = <span class="String">"externs"</span>,
srcs = [<span class="String">"externs.d.ts"</span>],
)
ts_library(
name = <span class="String">"common"</span>,
srcs = [<span class="String">"common.ts"</span>],
deps = [<span class="String">":externs"</span>],
)
ts_binary(
name = <span class="String">"main"</span>
srcs = [<span class="String">"main.ts"</span>],
deps = [<span class="String">":common"</span>],
flags = [
<span class="String">"--removeComments"</span>,
<span class="String">"--noEmitOnError"</span>,
],
)
</pre>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-9100824746800598084.post-19144695659372960582015-05-18T16:27:00.000-07:002015-05-18T16:27:18.499-07:00A quick update on WordGap<a href="http://www.nimbledev.com/">Nimble Development</a> from Central Oregon has acquired the domain wordgap.com from me to promote their mobile game <i>Word Gap</i>. The game is Windows 8 only, but the developers have started porting it to iOS and Android.<br />
<br />
My original anagram search app for English and French word games is still accessible from <a href="http://word-gap.appspot.com/">https://word-gap.appspot.com/</a>.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-9100824746800598084.post-10006785001272481762013-02-18T18:54:00.000-08:002013-02-18T18:54:09.889-08:00Function-array Type Signature in TypeScript<style type="text/css">
<!--
pre { white-space: pre-wrap; font-family: monospace; color: #e0eee0; background-color: #000000; padding:8px; font-size:13px}
.Exception { color: #90ee90; background-color: #000000; }
.Statement { color: #90ee90; }
.Special { color: #999999; }
.Function { color: #9bcd9b; background-color: #000000; }
.Type { color: #add8e6; }
.Operator { color: #7fff00; background-color: #000000; }
.Identifier { color: #8db6cd; }
.Keyword { color: #90ee90; background-color: #000000; }
.bnf {font-style:italic;}
-->
</style>
<p>In TypeScript, a function's type can be specified by a function type literal:</p>
<p class="bnf">FunctionType:<br/> ( ParameterList<sub>opt</sub> ) => ReturnType</p>
<p>For example, the following type signature specifies a unary function that takes a number and returns a number:</p>
<pre>
<span class="Identifier">var</span> unary: <span class="Operator">(</span>x: <span class="Type">number</span><span class="Operator">)</span> <span class="Operator">=></span> <span class="Type">number</span><span class="Exception">;</span>
</pre>
<p>TypeScript also supports array type literals that consist of an element type followed by a pair of brackets:</p>
<p class="bnf">ArrayType:<br/> Type [ ]</p>
<p>Now, suppose we need an array of unary functions, what would be the type signature of the function array?</p>
<p>Appending a pair of brackets to the previous function type literal wouldn't work because the resulting type signature, though syntactically correct, defines a single function that returns an array of numbers rather than an array of functions, each returning a single number:</p>
<pre>
<span class="Identifier">var</span> wrong: <span class="Operator">(</span>x: <span class="Type">number</span><span class="Operator">)</span> <span class="Operator">=></span> <span class="Type">number</span> <span class="Function">[]</span><span class="Exception">;</span>
</pre>
<p>Trying to disambiguate the semantics by wrapping the function type literal in parentheses is also erroneous — the first left parenthesis would be matched as the initial token of a <span class="bnf">FunctionType</span> rule, therefore the compiler would flag the second left parenthesis as a grammar error:</p>
<pre>
<span class="Identifier">var</span> wrongToo: <span class="Operator">(</span> <span class="Operator">(</span>x: <span class="Type">number</span><span class="Operator">)</span> <span class="Operator">=></span> <span class="Type">number</span> <span class="Operator">)</span> <span class="Function">[]</span><span class="Exception">;</span>
</pre>
<p>The key to solving this is the realization that a function type literal of the form <span class="bnf">( ParameterList<sub>opt</sub> ) => ResultType</span> can be rewritten as the equivalent object type literal <span class="bnf">{ ( ParameterList<sub>opt</sub> ) : ResultType; }</span>, which removes the ambiguity in a function array type signature. For example:
</p>
<pre>
<span class="Identifier">var</span> correct: <span class="Function">{</span><span class="Operator">(</span>x: <span class="Type">number</span><span class="Operator">)</span>: <span class="Type">number</span><span class="Exception">;</span><span class="Function">}</span><span class="Function">[]</span><span class="Exception">;</span>
</pre>
<p>To improve code readability, we may define a separate type name and reference it in the array type:</p>
<pre>
<span class="Keyword">interface</span> UnaryFunc <span class="Function">{</span>
<span class="Operator">(</span>x: <span class="Type">number</span><span class="Operator">)</span>: <span class="Type">number</span><span class="Exception">;</span>
<span class="Function">}</span>
<span class="Identifier">var</span> unaryFuncArray: UnaryFunc<span class="Function">[]</span><span class="Exception">;</span>
</pre>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-9100824746800598084.post-10130432305133781362012-03-29T22:20:00.001-07:002012-03-31T22:43:33.723-07:00Migrated WordGap from Python to Go 1<a href="http://www.wordgap.com/">WordGap</a>, an anagram search tool running on Google AppEngine, was originally written in Python. I recently rewrote it in Go 1 and added support for the SOWPODS word list. In terms of number of lines, the code size is roughly the same as the Python version, but the run-time performance has gained a considerable boost: the average latency of anagramming requests has dropped from ~<b>500</b>ms to ~<b>30</b>ms! Go has strongly exceeded my expectation and is now my language of choice for AppEngine app development.Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-9100824746800598084.post-84263846276972070222012-03-18T17:08:00.000-07:002012-03-18T17:08:05.071-07:00Initializing large arrays in Go for AppEngineGo supports <a href="http://golang.org/doc/go_spec.html#Composite_literals">composite literals</a> to construct arrays, slices, or maps. A list of strings, for example, may be created as follows:<br />
<style type="text/css">
pre { white-space: pre-wrap; font-family: monospace; color: #ffffff; background-color: #0b1022;
font-family: monospace; color: #ffffff; background-color: #0b1022; padding:8px;font-size:13px}
.String { color: #00d42d; }
.Type { color: #84a7c1; font-weight: bold; }
.Special { color: #ffa500; }
.Keyword { color: #ffde00; }
.Statement { color: #ffff60; font-weight: bold; }
</style><pre>words := []<span class="Type">string</span>{<span class="String">"AA"</span>, <span class="String">"AAH"</span>, <span class="String">"AAHED"</span>}
</pre>This is convenient and works really well for small lists. If you try to initialize an array with hundreds of thousands of elements, though, Go compilation will significantly slow down even if the entire list can comfortably fit into the memory. Worse yet, you won't be able to deploy your app to AppEngine. A typical error message looks like this:<br />
<pre>Error 422: --- begin server output ---
Compile failed:
2012/03/18 00:00:00 go-app-builder: Failed building app: failed running 6g: signal 9
--- end server output ---
Rolling back the update.
Error 422: --- begin server output ---
</pre>The solution is to construct large data structures at run-time by parsing data encoded in external files. For example, store a long list of words in a text file, one word per line; during initialization, convert them into a string slice:<br />
<pre><span class="Statement">import</span> (
<span class="String">"io/ioutil"</span>
<span class="String">"strings"</span>
)
<span class="Keyword">func</span> loadStringList(filename <span class="Type">string</span>) []<span class="Type">string</span> {
content, err := ioutil.ReadFile(filename)
<span class="Statement">if</span> err != <span class="Keyword">nil</span> {
<span class="Keyword">panic</span>(err)
}
<span class="Statement">return</span> strings.Split(<span class="Type">string</span>(content), <span class="String">"</span><span class="Special">\n</span><span class="String">"</span>)
}
<span class="Keyword">func</span> init() {
words := loadStringList(<span class="String">"words.txt"</span>)
...
}
</pre>For more complicated data structures, you may want to use the <a href="http://golang.org/pkg/gob/">gob</a> package to do efficient deserialization.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-9100824746800598084.post-25946589788554379352012-02-02T00:57:00.000-08:002012-02-02T00:57:14.198-08:00AppEngine Go SDK's 500-byte string limitationAppEngine Go SDK has an undocumented limitation of datastore strings: they must not exceed 500 bytes. The dev_appserver will happily accept Go structs with strings longer than this limit, but when you try to deploy your app or list datastore entities from the development console, you will encounter an error like this:<br />
<br />
<code style="font-size:11px">BadValueError: Property Value is 501 bytes long; it must be 500 or less. Consider Text instead, which can store strings of any length.</code><br />
<br />
To store long strings in the datastore, you'll need to manually convert a string type to a byte slice (<code style="font-size:11px">[]byte</code>), which will be stored as an <b>unindexed</b> blob. For example:<pre><code style="font-size:11px">
type T struct {
LongString []byte
}
func store(longString string, r *http.Request) os.Error {
ctx := appengine.NewContext(r)
key := datastore.NewIncompleteKey(ctx, "T", nil)
t := new(T)
t.LongString = []byte(longString)
_, e := datastore.Put(ctx, key, t)
return e
}
</code></pre>Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-9100824746800598084.post-15003572761432172882012-01-01T16:43:00.000-08:002012-01-01T16:43:39.330-08:00WordGap Chrome Web Store experimentAs a Chrome browser user, I don't see much value in <a href="https://chrome.google.com/webstore/category/home">Chrome Web Store</a>, which is essentially a modern day Dmoz directory of web pages wrapped in a thin layer and marketed as "Apps". But my judgment might be clouded by my general distaste for the Apps/AppStore hype. Perhaps many Chrome users do enjoy browsing the flashy App directory, in which case, Chrome Web Store might be a great channel for web apps to get more exposure.<br />
<br />
To test this hypothesis, I skimmed through the well-documented <a href="http://code.google.com/chrome/webstore/docs/index.html">Developer's Guide</a>, packaged <a href="http://www.wordgap.com/">WordGap</a> as an app within two hours (mainly spent on struggling on artworks with my limited Photoshopping skills), and published it on Chrome Web Store under the "Games" and "Utilities" categories. No additional efforts were made at all to promote the app.<br />
<br />
One month later, my <a href="https://chrome.google.com/webstore/developer/dashboard">Developer Dashboard</a> reports that the <a href="https://chrome.google.com/webstore/detail/hhlajfbhmmkkllnihcggmdbfenogpcjm">WordGap app</a> has garnered 32 weekly users and zero reviews.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiE9MhEFaynxNur58ikYWgNuBNprvkng7hR7DyNGi89-zzxXLRG8nQpeR9nwiVvvsIsXbPVRO2obVzLIY8nT_15vzxPMY3KUiqsN5C5D5kAbclT84AYbXhglcQApV-acp43dIw0CAEp7kA/s1600/wordgaponchromewebstore.png" imageanchor="1" style=""><img border="0" height="136" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiE9MhEFaynxNur58ikYWgNuBNprvkng7hR7DyNGi89-zzxXLRG8nQpeR9nwiVvvsIsXbPVRO2obVzLIY8nT_15vzxPMY3KUiqsN5C5D5kAbclT84AYbXhglcQApV-acp43dIw0CAEp7kA/s400/wordgaponchromewebstore.png" /></a></div><br />
Terribly pathetic numbers, if you ask me. Meanwhile, considering that Chrome Web Store doesn't offer an RSS feed or category for newly published apps, I'm curious to learn how these 32 weekly users discovered the WordGap app which is buried under heaps of blinding images. Did they navigate through pages after pages of apps until stumbling upon mine? Did they search for "anagram" or "scrabble" and somehow were dissatisfied by all the results ranked above my app? Did they find it under the "related apps" list? Or does Chrome Web Store randomize its directory ranking so that new apps get a chance to surface above the fold on a lucky day? Unfortunately, none of the questions can be answered by existing tools and APIs yet.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-9100824746800598084.post-14857684154149805162011-11-09T00:02:00.000-08:002011-11-09T00:55:09.864-08:00How to install the SSL module for the AppEngine SDK on OS XAppEngine SDKs for Python and Go both depend on the Python 2.5 runtime, which does not include the ssl module out of the box. Without the ssl module, you'll see the following warnings when you run dev_appserver.py or appcfg.py:<br />
<br />
<code style="font-size:10px">WARNING urlfetch_stub.py:111 No ssl package found. urlfetch will not be able to validate SSL certificates.</code><br />
<br />
<code style="font-size:10px">WARNING appengine_rpc.py:435 ssl module not found.<br />
Without the ssl module, the identity of the remote host cannot be verified, and<br />
connections may NOT be secure. To fix this, please install the ssl module from<br />
http://pypi.python.org/pypi/ssl .</code><br />
<br />
Here's how to fix it.<br />
<ol><li>Download the <a href="http://pypi.python.org/packages/source/s/ssl/ssl-1.15.tar.gz">ssl source</a> for Python 2.5 from <a href="http://pypi.python.org/pypi/ssl#downloads">http://pypi.python.org/pypi/ssl#downloads</a> and uncompress the package.<br />
</li>
<li>From that directory, run:<br />
<div style="font-size:11px;font-family:Courier;padding:4px;background:#f8f8f8;">CC='/usr/bin/gcc-4.0' python2.5 setup.py build<br />
<br />
sudo python2.5 setup.py install</div></li>
<li>Done. No more pesky security warnings.<br />
</li>
</ol>Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-9100824746800598084.post-61973797547196325842011-09-18T00:11:00.000-07:002016-01-13T12:39:44.292-08:00Go Template ExamplesThe Go language's <a href="http://golang.org/pkg/template/">template</a> package has introduced a set of new APIs in r60. (To keep using the <a href="http://golang.org/pkg/old/template/">old template</a> package, you'll need to change <span style="font-family:Courier;font-size:11px;background:#f8f8f8;padding:4px"><span style="color:#008000">import</span> <span style="color:#BA2121">"template"</span></span> to <span style="font-family:Courier;font-size:11px;background:#f8f8f8;padding:4px"><span style="color:#008000">import</span> <span style="color:#BA2121">"old/template"</span></span>.) This post includes a collection of code snippets to illustrate how to use the new template package.<br />
<br />
<h4>Parse a template string</h4><pre style="line-height:125%;font-size:11px;background:#f8f8f8;padding:4px">t, _ := template.New(<span style="color: #BA2121">"template_name"</span>).Parse(<span style="color: #BA2121">"Go"</span>)
t.Execute(os.Stdout, nil)
<span style="color: #408080; font-style: italic">// output: Go</span>
</pre><br />
<h4>Parse a template file</h4><pre style="line-height:125%;font-size:11px;background:#f8f8f8;padding:4px">t, _ := template.New(<span style="color: #BA2121">"template_name"</span>).ParseFile(<span style="color: #BA2121">"external.file"</span>)
t.Execute(os.Stdout, nil)
<span style="color: #408080; font-style: italic">// output: content of external.file</span>
</pre><br />
<h4>Print to a string buffer</h4><pre style="line-height:125%;font-size:11px;background:#f8f8f8;padding:4px">t, _ := template.New(<span style="color: #BA2121">"template_name"</span>).Parse(<span style="color: #BA2121">"Go"</span>)
buf := <span style="color: #008000">new</span>(bytes.Buffer)
t.Execute(buf, nil)
<span style="color: #408080; font-style: italic">// buf.String() == "Go"</span>
</pre><br />
<h4>Substitute a single parameter</h4><pre style="line-height:125%;font-size:11px;background:#f8f8f8;padding:4px">t, _ := template.New(<span style="color: #BA2121">"template_name"</span>).Parse(<span style="color: #BA2121">"<h1>{{.}}</h1>"</span>)
t.Execute(os.Stdout, <span style="color: #BA2121">"Joseki"</span>)
<span style="color: #408080; font-style: italic">// output: <h1>Joseki</h1></span>
</pre><br />
<h4>Substitute multiple parameters in a <i style="color:#008000">struct</i></h4><pre style="line-height:125%;font-size:11px;background:#f8f8f8;padding:4px"><span style="color: #008000; font-weight: bold">type</span> dict <span style="color: #008000; font-weight: bold">struct</span> {
<span style="color: #408080; font-style: italic">// struct fields must be public</span>
Title <span style="color: #008000">string</span>
Release <span style="color: #008000">int</span>
}
params := &dict{Title: <span style="color: #BA2121">"Go"</span>, Release: <span style="color: #666666">60</span>}
t, _ := template.New(<span style="color: #BA2121">"template_name"</span>).Parse(
<span style="color: #BA2121">"<h1>{{.Title}}</h1>r{{.Release}}"</span>)
t.Execute(os.Stdout, params)
<span style="color: #408080; font-style: italic">// output: <h1>Go</h1>r60</span>
</pre><br />
<h4>Substitute multiple parameters in a <i style="color:#008000">map</i></h4><pre style="line-height:125%;font-size:11px;background:#f8f8f8;padding:4px">t, _ := template.New(<span style="color: #BA2121">"template_name"</span>).Parse(
<span style="color: #BA2121">"<h1>{{.title}}</h1>r{{.release}}"</span>)
<span style="color: #408080; font-style: italic">// field names don't have to be capitalized</span>
params := <span style="color: #008000; font-weight: bold">map</span>[<span style="color: #008000">string</span>]<span style="color: #008000; font-weight: bold">interface</span>{}{<span style="color: #BA2121">"title"</span>: <span style="color: #BA2121">"Go"</span>, <span style="color: #BA2121">"release"</span>: <span style="color: #666666">60</span>}
t.Execute(os.Stdout, params)
<span style="color: #408080; font-style: italic">// output: <h1>Go</h1>r60</span>
</pre><br />
<h4>Conditionally fill in parameters</h4><pre style="line-height:125%;font-size:11px;background:#f8f8f8;padding:4px">t, _ := template.New(<span style="color: #BA2121">"template_name"</span>).Parse(
<span style="color: #BA2121">"{{if .white}}Gote{{else}}Sente{{end}}{{if .excl}}!{{end}}"</span>)
t.Execute(os.Stdout, <span style="color: #008000; font-weight: bold">map</span>[<span style="color: #008000">string</span>]bool{<span style="color: #BA2121">"excl"</span>: true})
<span style="color: #408080; font-style: italic">// output: Sente!</span>
</pre><br />
<h4>Iteratively fill in parameters</h4><pre style="line-height:125%;font-size:11px;background:#f8f8f8;padding:4px">t, _ := template.New(<span style="color: #BA2121">"template_name"</span>).Parse(
<span style="color: #BA2121">"{{range .}}{{.}}!{{else}}Tengen{{end}}"</span>)
t.Execute(os.Stdout, [...]<span style="color: #008000">string</span>{<span style="color: #BA2121">"Hoshi"</span>, <span style="color: #BA2121">"Komoku"</span>, <span style="color: #BA2121">"Sansan"</span>})
<span style="color: #408080; font-style: italic">// output: Hoshi!Komoku!Sansan!</span>
</pre><br />
<h4>Escape parameters</h4><pre style="line-height:125%;font-size:11px;background:#f8f8f8;padding:4px">t, _ := template.New(<span style="color: #BA2121">"html"</span>).Parse(
<span style="color: #BA2121">"<a href='/?q={{.query | urlquery}}'>q={{.text | html}}</a>"</span>)
t.Execute(os.Stdout, <span style="color: #008000; font-weight: bold">map</span>[<span style="color: #008000">string</span>]<span style="color: #008000">string</span>{<span style="color: #BA2121">"text"</span>: <span style="color: #BA2121">"&"</span>, <span style="color: #BA2121">"query"</span>: <span style="color: #BA2121">"&"</span>})
<span style="color: #408080; font-style: italic">// output: <a href='/?q=%26'>q=&amp;</a></span>
</pre><br />
<h4><s>Nest templates</s> (<span style="color:red">deprecated API</span>)</h4><pre style="line-height:125%;font-size:11px;background:#f8f8f8;padding:4px">ts := <span style="color: #008000">new</span>(template.Set)
tparent, _ := template.New(<span style="color: #BA2121">"parent"</span>).Parse(
<span style="color: #BA2121">"<i>{{template \"child\" .}}</i>"</span>)
tchild, _ := template.New(<span style="color: #BA2121">"child"</span>).Parse(
<span style="color: #BA2121">"<b>{{.lang}}</b>"</span>)
ts.Add(tparent, tchild)
ts.Execute(os.Stdout, <span style="color: #BA2121">"parent"</span>, <span style="color: #008000; font-weight: bold">map</span>[<span style="color: #008000">string</span>]<span style="color: #008000">string</span>{<span style="color: #BA2121">"lang"</span>: <span style="color: #BA2121">"Go"</span>})
<span style="color: #408080; font-style: italic">// output: <i><b>Go</b></i></span>
</pre><br />
<h4><s>Parse a template set</s> (<span style="color:red">deprecated API</span>)</h4><pre style="line-height:125%;font-size:11px;background:#f8f8f8;padding:4px">ts := <span style="color: #008000">new</span>(template.Set)
ts.Parse(<span style="color: #BA2121">`{{define "parent"}}<i>{{template "child" .}}</i>{{end}}</span>
<span style="color: #BA2121"> {{define "child"}}<b>{{.lang}}</b>{{end}}`</span>)
ts.Execute(os.Stdout, <span style="color: #BA2121">"parent"</span>, <span style="color: #008000; font-weight: bold">map</span>[<span style="color: #008000">string</span>]<span style="color: #008000">string</span>{<span style="color: #BA2121">"lang"</span>: <span style="color: #BA2121">"Go"</span>})
<span style="color: #408080; font-style: italic">// output: <i><b>Go</b></i></span>
</pre><br />
<h4>Nested templates</h4>
<pre style="background:#f9f9f9;color:#080808">t, _ := <span style="color:#a71d5d;font-style:italic">template.New</span>(<span style="color:#0b6125">"parent"</span>).<span style="color:#a71d5d;font-style:italic">Parse</span>(<span style="color:#0b6125">`<i>{{template "child" .}}</i>`</span>)
t.<span style="color:#a71d5d;font-style:italic">New</span>(<span style="color:#0b6125">"child"</span>).<span style="color:#a71d5d;font-style:italic">Parse</span>(<span style="color:#0b6125">`<b>{{.lang}}</b>`</span>)
t.<span style="color:#a71d5d;font-style:italic">ExecuteTemplate</span>(<span style="color:#a71d5d;font-style:italic">os.Stdout</span>, <span style="color:#0b6125">"parent"</span>, map[string]string{<span style="color:#0b6125">"lang"</span>: <span style="color:#0b6125">"Go"</span>})
<span style="color:#5a525f;font-style:italic">// output: <i><b>Go</b></i></span>
</pre>
<h4>Defined templates</h4>
<pre style="background:#f9f9f9;color:#080808">t, _ := <span style="color:#a71d5d;font-style:italic">template.New</span>(<span style="color:#0b6125">""</span>).<span style="color:#a71d5d;font-style:italic">Parse</span>(
<span style="color:#0b6125">`{{define "golang"}}golang{{end}} {{define "go"}}Go{{end}}`</span>)
t.<span style="color:#a71d5d;font-style:italic">ExecuteTemplate</span>(<span style="color:#a71d5d;font-style:italic">os.Stdout</span>, <span style="color:#0b6125">"go"</span>, nil)
<span style="color:#5a525f;font-style:italic">// output: Go</span>
</pre>
The template package also offers a few more APIs not covered by the examples. Please refer to <a href="https://golang.org/pkg/text/template/">the documentation</a> for more information.Unknownnoreply@blogger.com4tag:blogger.com,1999:blog-9100824746800598084.post-57873768415810127832011-06-21T01:36:00.000-07:002011-06-21T20:37:49.840-07:00Saaguan ParadoxA new entry for <a href="http://danielsolisblog.blogspot.com/2010/12/thousand-year-game-design-challenge.html">Daniel Solis' thousand year game design challenge</a> caught my attention today. It's an interesting abstract board game called <a href="http://andrew.cooke.org/Saaguan/Saaguan/1000YearChallenge/Saaguan1000YC.pdf">Saaguan</a>, designed by Andrew Cooke.<br />
<br />
Full rules can be found <a href="http://andrew.cooke.org/Saaguan/Saaguan/1000YearChallenge/Saaguan1000YC.pdf">here</a>. In a nutshell, players maneuver their robots on an oct-board or hex-board to kill other robots. Each robot emits a beam toward where it's heading for, threatening enemy bots along the line. If a bot is threatened by two enemy bots, it gets "locked down", and its beam is immediately disabled; if a bot is threatened by three enemies, it gets killed and immediately removed from the board. On each turn, a player can either add a new bot to the board or make three movements of existing bots of their own by rotation or advancement.<br />
<br />
I was quickly intrigued by Saaguan's simple rules and the difficulty to write a decent A.I. player due to its huge branching factor even on very small boards. When I started to mull over an algorithm to detect locked down and dead bots, it then occurred to me that certain contrived bot move sequences would expose an ambiguity in Saaguan's rules.<br />
<br />
Let's consider an example on an oct-board.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixNVC5X_a3odA3or2_qSZo6RZLKP0ai7oTJpU5me5XGHDwuyw4WEC1C81vSDxLoN5JJk2nsihIhHvxE34072aXFfaFU26Tdz8ltN18apR35FNQly95AV4Trw8JKV_xvNxxq8jvliYGSCY/s1600/saaguan_paradox_1.png" imageanchor="1" style=""><img border="0" height="205" width="205" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixNVC5X_a3odA3or2_qSZo6RZLKP0ai7oTJpU5me5XGHDwuyw4WEC1C81vSDxLoN5JJk2nsihIhHvxE34072aXFfaFU26Tdz8ltN18apR35FNQly95AV4Trw8JKV_xvNxxq8jvliYGSCY/s400/saaguan_paradox_1.png" /></a></div><br />
In the previous diagram, blue bot #2 and #4 both threaten red bot #3, therefore locking it down (marked by a black border).<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIkQuB6D_3vyTJKhJbvhjpSf2z8jvGJTqC5hgZ2k3HMMLq5IsiKpxBAiSfsv-R4X85Fm1JJXbbT3Ep910TCLliWlKLg7fUEPUR_nGAGR3Me3ZXLwkLVzijIkXIzX6vBv41mnJt2-DcK2I/s1600/saaguan_paradox_2.png" imageanchor="1" style=""><img border="0" height="205" width="205" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIkQuB6D_3vyTJKhJbvhjpSf2z8jvGJTqC5hgZ2k3HMMLq5IsiKpxBAiSfsv-R4X85Fm1JJXbbT3Ep910TCLliWlKLg7fUEPUR_nGAGR3Me3ZXLwkLVzijIkXIzX6vBv41mnJt2-DcK2I/s400/saaguan_paradox_2.png" /></a></div><br />
When bot #5 and #6 join the battlefield as shown above, blue bot #2 gets locked down, freeing red bot #3.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdXiiAMelZPQsIj4MP91SIG6DNW23cMH0MptOaZpQc58eYfbsekeLABBw6jusU-b_uWYPf7JUrJe3kC6_9frqmOI_uiwuX_qN6MZY_D9jI2vvhZZ7Ca-Tn8ivRMeyh6fde2eHZuhyOCXo/s1600/saaguan_paradox_3.png" imageanchor="1" style=""><img border="0" height="205" width="205" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdXiiAMelZPQsIj4MP91SIG6DNW23cMH0MptOaZpQc58eYfbsekeLABBw6jusU-b_uWYPf7JUrJe3kC6_9frqmOI_uiwuX_qN6MZY_D9jI2vvhZZ7Ca-Tn8ivRMeyh6fde2eHZuhyOCXo/s400/saaguan_paradox_3.png" /></a></div><br />
Blue turns the table again with the reinforcement of bot #7.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlaINGqho2GhLMvA18ERLCP4rQOOXalitUelcQXUPgeI07MTaJO1T7nhxuEVK57PYXVdkaBWAcsU0QiQbxCPpN4AjHHPTdozlCUVe51uCGu-5v8-08ZCM5X4Y7OfNlF7dmgQ-UIEO_xX8/s1600/saaguan_paradox_4.png" imageanchor="1" style=""><img border="0" height="205" width="205" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlaINGqho2GhLMvA18ERLCP4rQOOXalitUelcQXUPgeI07MTaJO1T7nhxuEVK57PYXVdkaBWAcsU0QiQbxCPpN4AjHHPTdozlCUVe51uCGu-5v8-08ZCM5X4Y7OfNlF7dmgQ-UIEO_xX8/s400/saaguan_paradox_4.png" /></a></div><br />
The addition of red bot #8 rescues neither #3 nor #6 as red is short of one more beam to lock down blue bot #7.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnoamX1hyxY7SWcdLeIW6BjI1ths6bznTAkck5_Q4ztVxnQET7TAzyzitlvrLBxIlpceSH3gPcdBdYQZ7GzHGWpqglExhw2-CCIJhK5ELhYPaiT3R4cCnJQgbvf1hZupXBVrFHdCccz2c/s1600/saaguan_paradox_5.png" imageanchor="1" style=""><img border="0" height="205" width="205" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnoamX1hyxY7SWcdLeIW6BjI1ths6bznTAkck5_Q4ztVxnQET7TAzyzitlvrLBxIlpceSH3gPcdBdYQZ7GzHGWpqglExhw2-CCIJhK5ELhYPaiT3R4cCnJQgbvf1hZupXBVrFHdCccz2c/s400/saaguan_paradox_5.png" /></a></div><br />
Here comes an interesting turning point: What happens if blue bot #2 now rotates by 90 degrees?<br />
<br />
One might examine bot #3 first and conclude that since #2 is no longer threatening it, #3 becomes active and locks down #7 with the help of #8. One might even argue that before #2 reorients its beam downward, #6 has a chance to lock down #2 together with #1 since #7's threat is dissolved.<br />
<br />
On the other hand, one might also check the state of bot #6 first and claim that it's under a three-way attack from #2, #5, #7 and should be immediately killed.<br />
<br />
As this example has shown, the order of state changes is critical in evaluating delicate Saaguan situations, which hopefully will be addressed by an updated rule set.<br />
<br />
<b>Update</b><br />
<br />
Andrew has already disambiguated the rules on <a href="http://andrew.cooke.org/Saaguan/Saaguan/Main/SaaguanRules.pdf">his site</a>. That was fast!<br />
<br />
Also, I made a mistake in the original example — robots on octboards rotate by 45° on each turn, not 90°. An updated example follows:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiu7sQYFul7sQ1owztmhKLTfQ1ak3DpQoZ2SyZ2SgMjalNwKL48X5uOOz-exRlQ8qT916AJoTFLO8kEASeNK6wqgvDcxXKm7t7iq5KHkyYqFz9UoTUBvRpJxvPeSr8EsWEgvELfXm15n44/s1600/saaguan_octboard_1.png" imageanchor="1" style=""><img border="0" height="221" width="221" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiu7sQYFul7sQ1owztmhKLTfQ1ak3DpQoZ2SyZ2SgMjalNwKL48X5uOOz-exRlQ8qT916AJoTFLO8kEASeNK6wqgvDcxXKm7t7iq5KHkyYqFz9UoTUBvRpJxvPeSr8EsWEgvELfXm15n44/s400/saaguan_octboard_1.png" /></a></div><br />
Red-a is locked down by Blue-b and Blue-c; Red-d by Blue-e and Blue-g. Now Blue-c rotates clockwise by 45°. Depending on the order of state evaluation, either Red-d or Blue-e could be said to be shut down. According to the new rules — if I'm interpreting them correctly — once Blue-c starts to rotate, Red-a gets activated, thus locking down Blue-e, which in turn activates Red-d. Blue-e then gets shut down before Blue-c points to the right and locks down Red-d.Unknownnoreply@blogger.com10tag:blogger.com,1999:blog-9100824746800598084.post-37513030237794473942011-03-30T23:09:00.001-07:002011-03-30T23:10:10.400-07:00Creating large in-memory database on AppEngineFor high performance AppEngine app development, it often comes in handy to store read-only database in memory. Accessing in-memory data is not only orders of magnitude faster than making DataStore or memcached calls but also a lot cheaper as it does not incur any API cost.<br />
<br />
However, if you encode data as a Python dictionary or list with a million entries, your app will most likely crash on AppEngine, throwing a distasteful <em>exceptions.MemoryError</em>: <em>Exceeded soft process size limit with 299.98 MB</em>. "But I'm only loading 10MB of data!", you proclaim. Unfortunately, Python may temporarily consume over a gigabyte of memory while parsing and constructing a multi-megabyte dictionary or list.<br />
<br />
The first thing you should consider is to simplify the data structure. If possible, flatten your database into one-dimensional lists, which enjoy a smaller memory footprint than dictionaries and multi-level nested lists.<br />
<br />
Next, try data serialization using the <a href="http://docs.python.org/library/pickle.html">pickle</a> library. Be sure to use protocol version 2 for maximum efficiency and compactness. For example:<br />
<code style="font-size:10px;"># To serialize data<br />
pickle.dump(data, open('data.bin', 'w'), pickle.HIGHEST_PROTOCOL)<br />
# To deserialize data<br />
data = pickle.load(open(os.path.join(os.path.dirname(__file__), 'data.bin'), 'r'))</code><br />
<br />
As AppEngine does not support the much faster cPickle module ("cPickle" is aliased to "pickle" on AppEngine), your app may time out if you try to unpickle millions of records. One effective solution is to store your data in <a href="http://docs.python.org/library/array.html">homogeneous arrays</a> to take advantage of array's highly efficient serialization implementation. Suppose you have a list of a million signed integers, you may first convert the list into a typed array and save it in a binary file:<br />
<code style="font-size:10px;">array.array('i', data).tofile(open('data.bin', 'w'))</code><br />
Deserializing the array literally takes just a few milliseconds on AppEngine:<br />
<code style="font-size:10px;">data = array.array('i')<br />
data.fromfile(open(os.path.join(os.path.dirname(__file__), 'data.bin'), 'r'), 1000000)</code><br />
<br />
One caveat: To load more than 10MB of data, you will have to split the database into multiple files to work around AppEngine's <a href="http://code.google.com/appengine/docs/python/runtime.html#Quotas_and_Limits">size limit of static files</a>.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-9100824746800598084.post-35943225925066259702011-03-06T22:27:00.000-08:002015-05-18T14:28:21.264-07:00Book Notes: Pragmatic Thinking and LearningIn his book <a href="http://www.amazon.com/gp/product/1934356050?ie=UTF8&tag=23-15-20&linkCode=as2&camp=1789&creative=390957&creativeASIN=1934356050">Pragmatic Thinking and Learning: Refactor Your Wetware</a>, Andy Hunt explores interesting aspects of how human brain works, and more importantly, how we can rewire our "wetware" to be more effective learners.<br />
<br />
<br />
<h3>Tip 1. Always consider the context.</h3>Nothing exists in isolation.<br />
<br />
<hr/>Five stages on the journey from novice to expert:<br />
<ul><li>Novices, who need fixed rules to follow.</li>
<li>Advanced beginners, who can try new tasks on their own but have no holistic understanding yet.</li>
<li>Competent, who can solve novel problems and apply advice from experts.</li>
<li>Proficient, who can evaluate their past performance and self-correct.</li>
<li>Expert, who can tap into a vast body of experience and work from intuition.</li>
</ul><hr/><h3>Tip 2. Use rules for novices, intuition for experts.</h3><br />
<h4>Tip 3. Know what you don't know.</h4><br />
<hr/>Deliberate practice requires four conditions:<br />
<ul><li>You need a well-defined task.</li>
<li>The task needs to be challenging but doable.</li>
<li>The environment needs to supply informative feedback.</li>
<li>It should also provide opportunities for repetition and correction of errors.</li>
</ul><hr/><h3>Tip 4. Learn by watching and imitating.</h3><ul><li>Imitate</li>
<li>Assimilate</li>
<li>Innovate</li>
</ul><br />
<h3>Tip 5. Keep practicing in order to remain expert.</h3><br />
<h4>Tip 6. Avoid formal methods if you need creativity, intuition, or inventiveness.</h4><br />
<h3>Tip 7. Learn the skill of learning.</h3>Understanding skills acquisition is a skill itself.<br />
<br />
<hr/>Your brain has two modes (commonly known as left-brain and right-brain thinking):<br />
<ul><li>L-mode (linear processing mode) is critical for sequential reasoning and problem solving.</li>
<li>R-mode (asynchronous, holistic, rich mode) is responsible for intuition and creativity.</li>
</ul><hr/><h3>Tip 8. Capture all ideas to get more of them.</h3>Take advantage of external, preferably digital information organizers.<br />
<br />
<h4>Tip 9. Learn by synthesis as well as by analysis.</h4><br />
<h3>Tip 10. Strive for good design; It really works better.</h3>Creativity comes from the selection and assembly of the right components in the right presentation in the right context.<br />
<br />
<h3>Tip 11. Rewire your brain with belief and constant practice.</h3>Just thinking that your brain has more capacity for learning makes it so.<br />
<br />
<h4>Tip 12. Add sensory experience to engage more of your brain.</h4><br />
<h4>Tip 13. Lead with R-mode; follow with L-mode.</h4><br />
<h4>Tip 14. Use metaphor as the meeting place between R-mode and L-mode.</h4><br />
<h4>Tip 15. Cultivate humor to build stronger metaphors.</h4><br />
<hr/>The Morning Pages Technique for writers:<br />
<ul><li>Write your morning pages first thing in the morning.</li>
<li>Write at least three pages.</li>
<li>Do not censor what your write.</li>
<li>Do not skip a day.</li>
</ul><hr/><h3>Tip 16. Step away from the keyboard to solve hard problems.</h3>Yoga, meditation, breathing techniques, and martial arts all affect how your brain processes information.<br />
<br />
<h4>Tip 17. Change your viewpoint to solve the problem.</h4><br />
<hr/>Common cognitive biases to be aware of:<br />
<ul><li>Anchoring</li>
<li>Fundamental attribution error</li>
<li>Self-serving bias</li>
<li>Need for closure</li>
<li>Confirmation bias</li>
<li>Exposure effect</li>
<li>Hawthorne effect</li>
<li>False memory</li>
<li>Symbolic reduction fallacy</li>
<li>Nominal fallacy</li>
<li>Confusion of correlation and causation</li>
</ul><hr/><h4>Tip 18. Watch the outliers; "rarely" doesn't mean "never".</h4><br />
<h4>Tip 19. Be comfortable with uncertainty.</h4><br />
<h4>Tip 20. Trust ink over memory; every mental read is a write.</h4><br />
<h4>Tip 21. Hedge your bets with diversity (of thinking to keep from falling victim to your generation's particular set of biases).</h4><br />
<h3>Tip 22. Allow for different bugs in different people.</h3>Don't try to change other people's temperament to match your own.<br />
<br />
<h4>Tip 23. Act like you've evolved; breathe, don't hiss.</h4>Let your lizard reaction pass. Control your emotions.<br />
<br />
<h4>Tip 24. Trust intuition, but verify.</h4><br />
<h3>Tip 25. Create SMART objectives to reach your goals.</h3>SMART objectives are:<br />
<ul><li>S - Specific</li>
<li>M - Measurable</li>
<li>A - Achievable</li>
<li>R - Relevant</li>
<li>T - Time-boxed</li>
</ul><br />
<h4>Tip 26. Plan your investment in learning deliberately.</h4><br />
<h4>Tip 27. Discover how you learn best (by experimenting with different learning modes).</h4>Myers-Briggs types are not destiny. You can always choose to act differently.<br />
<br />
<h4>Tip 28. Form study groups to learn and teach.</h4><br />
<h3>Tip 29. Read deliberately.</h3><ul><li>Survey the book in question to get a good overview without delving into any details.</li>
<li>Write down questions you want answered.</li>
<li>Read the book in its entirety; Recite, recall, rephrase, and reread important bits.</li>
<li>Finally review the material; Take notes and maybe draw mind maps to help visualization of information.</li>
</ul><br />
<h4>Tip 30. Take notes.</h4><br />
<h4>Tip 31. Write on: documenting is more important than documentation.</h4><br />
<h3>Tip 32. See it. Do it. Teach it.</h3>Teaching forces you to constantly retrieve information which is critical for learning.<br />
<br />
<h4>Tip 33. Play more in order to learn more.</h4><br />
<h4>Tip 34. Learn from similarities; unlearn from differences.</h4><br />
<h4>Tip 35. Explore, invent, and apply in your environment.</h4><br />
<h3>Tip 36. See without judging and then act.</h3>Cultivate nonjudgmental awareness; don't try to get it right the first time, but notice when it goes wrong.<br />
<br />
<h3>Tip 37. Give yourself permission to fail; It's the path to success.</h3><br />
<h3>Tip 38. Groove your mind for success.</h3>Create mental conditions that you'd experience once you learn to perform at a higher level.<br />
<br />
<h3>Tip 39. Learn to pay attention.</h3>Learn the basics of meditation to more efficiently allocate your limited "attentional resources".<br />
<br />
<h3>Tip 40. Make thinking time.</h3>It takes time to marinate ideas. Sitting around doing nothing is part of the creative process.<br />
<br />
<h4>Tip 41. Use a wiki to manage information and knowledge.</h4><br />
<h3>Tip 42. Establish rules of engagement to manage interruptions.</h3>Avoid distractions; reduce context switching cost.<br />
<br />
<h4>Tip 43. Send less email, and you'll receive less email.</h4><br />
<h4>Tip 44. Choose your own tempo for an email conversation.</h4><br />
<h4>Tip 45. Mask interrupts to maintain focus.</h4><br />
<h4>Tip 46. Use multiple monitors to avoid context switching.</h4><br />
<h4>Tip 47. Optimize your personal workflow.</h4><br />
<hr/>Change is hard. Suggestions to help you manage effective change:<br />
<ul><li>Start with a plan.</li>
<li>Avoid inaction.</li>
<li>Take time to develop new habits.</li>
<li>Belief is real. You have to believe that change is possible.</li>
<li>Take small, next steps. Choose a small, achievable goal, and reward yourself for reaching it. Rinse and repeat.</li>
</ul><hr/><h3>Tip 48. Grab the wheel. You can't steer on autopilot.</h3>You need to constantly reevaluate yourself and your condition.<br />
<hr/>Reading <a href="http://www.amazon.com/gp/product/1934356050?ie=UTF8&tag=23-15-20&linkCode=as2&camp=1789&creative=390957&creativeASIN=1934356050">Pragmatic Thinking and Learning: Refactor Your Wetware</a> didn't reshape my thinking habits overnight, but it definitely helped me develop a higher level of consciousness of my state of mind.Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-9100824746800598084.post-86076781462114568772011-01-31T23:42:00.000-08:002011-01-31T23:48:55.683-08:00Book Notes: High Performance Web SitesIn his 137-page book <a href="http://www.amazon.com/gp/product/0596529309?ie=UTF8&tag=23-15-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0596529309">High Performance Web Sites: Essential Knowledge for Front-End Engineers</a>, Steve Souders (who works at Google on web performance) presents 14 web site optimization rules supported by examples and code snippets.<br />
<br />
<h3>Rule 1: Make Fewer HTTP Requests</h3><ul><li>Use <a href="http://www.alistapart.com/articles/sprites">CSS Sprites</a> (or the less flexible <a href="en.wikipedia.org/wiki/Image_map">image maps</a>).</li>
<li>Use the <a href="en.wikipedia.org/wiki/Data_URI_scheme">data: URL scheme</a> (usually for inlining images but applicable anywhere a URL is specified, including the SCRIPT tag.)</li>
<li>Merge JavaScript; merge CSS files. Use a build system to do it automatically.</li>
</ul><br />
<h3>Rule 2: Use a Content Delivery Network</h3>Use <a href="en.wikipedia.org/wiki/Content_delivery_network">CDN</a> to host static content.<br />
<ul><li>Pros: improved response time, backup service, high storage capacity, better caching, resilient to traffic spikes.</li>
<li>Cons: affected by other CDN clients, indirect control of content servers, subject to the performance of CDN.</li>
</ul><br />
<h3>Rule 3: Add an Expires Header</h3><ul><li>Set a far future Expires HTTP header.<br />
(e.g. <code>Expires: Tue, 19 Jan 2037 00:00:00 GMT</code>)</li>
<li>Set a Cache-Control header.<br />
(e.g. <code>Cache-Control: max-age=315360000</code>)</li>
<li>Change links when resources change. Embed file revision number or fingerprint in the URL to bust the browser cache.</li>
</ul><br />
<h3>Rule 4: Gzip Components</h3><ul><li>Turn on gzip for highly compressible content such as HTML, JavaScript, and CSS.</li>
<li>Use mod_gzip for Apache 1.3 and mod_deflate for Apache 2.x.</li>
</ul><br />
<h3>Rule 5: Put Stylesheets at the Top</h3><ul><li>Put CSS in the document HEAD using the LINK tag.<br />
(e.g. <code><link rel="stylesheet" href="style.css"/></code>)</li>
</ul><br />
<h3>Rule 6: Put Scripts at the Bottom</h3><ul><li>Put SCRIPTs to the bottom of the page so that they don't block rendering and parallel downloads of other resources.</li>
</ul><br />
<h3>Rule 7: Avoid CSS Expressions</h3><ul><li>CSS expressions are generally slow and dangerous. (They are also <a href="http://msdn.microsoft.com/en-us/library/ms537634(v=vs.85).aspx">deprecated</a> in IE8.) Do not use them.</li>
</ul><br />
<h3>Rule 8: Make JavaScript and CSS External</h3><ul><li>Put large, reusable JavaScript and CSS in external files.</li>
</ul><br />
<h3>Rule 9: Reduce DNS Lookups</h3><ul><li>Reuse connections by setting <code>Connection: Keep-Alive</code> in the header.</li>
<li>Use fewer domains to reduce DNS lookups.</li>
</ul><br />
<h3>Rule 10: Minify JavaScript</h3><ul><li>Minify JavaScript source code using tools like <a href="code.google.com/closure/compiler">Closure Compiler</a> and <a href="www.crockford.com/javascript/jsmin.html">JSMin</a>.</li>
</ul><br />
<h3>Rule 11: Avoid Redirects</h3><ul><li>Avoid redirecting URL just because it's missing a trailing slash.</li>
<li>Not mentioned in the book, but it's better to serve both desktop and mobile versions of a page from the same URL.</li>
</ul><br />
<h3>Rule 12: Remove Duplicate Scripts</h3><ul><li>Make sure scripts are included only once.</li>
</ul><br />
<h3>Rule 13: Configure ETags</h3><ul><li>Either turn off <a href="en.wikipedia.org/wiki/HTTP_ETag">ETags</a> or generate ETags that uniquely identify a specific version of a resource. The default ETag formats of popular web servers are not cache-friendly.</li>
</ul><br />
<h3>Rule 14: Make Ajax Cacheable</h3><ul><li>Make Ajax response cacheable by following rule 3, 4, 9, 10, 11, 13.</li>
</ul><br />
<a href="http://www.amazon.com/gp/product/0596529309?ie=UTF8&tag=23-15-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0596529309">High Performance Web Sites</a> is short and easily digestible. It's a pertinent book on speed optimization for web developers, although professional web frontend engineers may find it short of original ideas they are not already aware of.Unknownnoreply@blogger.com0