<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css" href="http://garethrees.org/style/plain.css"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>garethrees.org</title>
<link>http://garethrees.org/</link>
<description></description>
<language>en</language>
<lastBuildDate>Tue, 14 May 2013 23:13:48 GMT</lastBuildDate>
<managingEditor>gareth.rees@pobox.com (Gareth Rees)</managingEditor>
<webMaster>gareth.rees@pobox.com (Gareth Rees)</webMaster>
<ttl>1440</ttl>
<atom:link href="http://garethrees.org/index_rss.xml" rel="self" type="application/rss+xml" />
<item>
<title>Circular logic</title>
<link>http://garethrees.org/2013/05/15/euler/</link>
<description>&lt;p&gt;&lt;a href="http://projecteuler.net/index.php?section=problems&amp;amp;id=209"&gt;Project Euler problem 209&lt;/a&gt; asks:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;How many 6-input binary truth tables, \(τ\), satisfy the formula $$ τ(a, b, c, d, e, f) ∧ τ(b, c, d, e, f, a ⊕ (b ∧ c)) = 0 $$ for all 6-bit inputs \((a, b, c, d, e, f)\)?&lt;/blockquote&gt;

&lt;p&gt;Spoilers below.&lt;/p&gt;

&lt;p class=centred&gt;★&lt;/p&gt;

&lt;p&gt;The formulae are all of the form \(τ(A) ∧ τ(B) = 0\) for two truth table rows \(A\) and \(B\). In other words, any truth table satisfying these formulae must assign true to at most one of \(τ(A)\) and \(τ(B)\). Each formula thus specifies a constraint joining two rows of the truth table. What happens if we plot all 64 truth table rows as nodes in a graph, with the constraints as edges? Each satisfying truth table would correspond to a colouring of the nodes of the graph with the colours black (representing true) and white (representing false), such that no two black nodes are adjacent.&lt;/p&gt;

&lt;p&gt;The web site &lt;a href=http://chart.ravenbrook.com&gt;chart.ravenbrook.com&lt;/a&gt; provides a handy way to draw this graph:&lt;/p&gt;

&lt;pre class=long&gt;&lt;code&gt;&lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;chart_url_209&lt;/span&gt;():
    &lt;span class=string&gt;"""Return a Ravenbrook Chart URL for the graph generated from Project
    Euler problem 209.

    """&lt;/span&gt;
    &lt;span class=variable-name&gt;edges&lt;/span&gt; = []
    &lt;span class=keyword&gt;for&lt;/span&gt; i &lt;span class=keyword&gt;in&lt;/span&gt; &lt;span class=builtin&gt;range&lt;/span&gt;(1 &amp;lt;&amp;lt; 6):
        &lt;span class=variable-name&gt;a&lt;/span&gt;, &lt;span class=variable-name&gt;b&lt;/span&gt;, &lt;span class=variable-name&gt;c&lt;/span&gt; = i &amp;gt;&amp;gt; 5 &amp;amp; 1, i &amp;gt;&amp;gt; 4 &amp;amp; 1, i &amp;gt;&amp;gt; 3 &amp;amp; 1
        &lt;span class=variable-name&gt;j&lt;/span&gt; = (i &amp;lt;&amp;lt; 1 &amp;amp; 63) + a ^ (b &amp;amp; c)
        &lt;span class=keyword&gt;if&lt;/span&gt; i != j: &lt;span class=comment-delimiter&gt;# &lt;/span&gt;&lt;span class=comment&gt;Limitation of chart.ravenbrook.com (no self edges)
&lt;/span&gt;            edges.append(&lt;span class=string&gt;'{},{}'&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(i, j))
    &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=string&gt;''&lt;/span&gt;.join([&lt;span class=string&gt;'http://chart.ravenbrook.com/chart?chs=1000x1000&amp;amp;chl='&lt;/span&gt;,
                    &lt;span class=string&gt;'|'&lt;/span&gt;.join(&lt;span class=string&gt;'{:06b}'&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(i) &lt;span class=keyword&gt;for&lt;/span&gt; i &lt;span class=keyword&gt;in&lt;/span&gt; &lt;span class=builtin&gt;range&lt;/span&gt;(1 &amp;lt;&amp;lt; 6)),
                    &lt;span class=string&gt;'&amp;amp;che='&lt;/span&gt;, &lt;span class=string&gt;'|'&lt;/span&gt;.join(edges)])&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And here’s &lt;a href="http://chart.ravenbrook.com/chart?chs=1000x1000&amp;amp;chl=000000|000001|000010|000011|000100|000101|000110|000111|001000|001001|001010|001011|001100|001101|001110|001111|010000|010001|010010|010011|010100|010101|010110|010111|011000|011001|011010|011011|011100|011101|011110|011111|100000|100001|100010|100011|100100|100101|100110|100111|101000|101001|101010|101011|101100|101101|101110|101111|110000|110001|110010|110011|110100|110101|110110|110111|111000|111001|111010|111011|111100|111101|111110|111111&amp;amp;che=1,2|2,4|3,6|4,8|5,10|6,12|7,14|8,16|9,18|10,20|11,22|12,24|13,26|14,28|15,30|16,32|17,34|18,36|19,38|20,40|21,42|22,44|23,46|24,49|25,51|26,53|27,55|28,57|29,59|30,61|31,63|32,1|33,3|34,5|35,7|36,9|37,11|38,13|39,15|40,17|41,19|42,21|43,23|44,25|45,27|46,29|47,31|48,33|49,35|50,37|51,39|52,41|53,43|54,45|55,47|56,48|57,50|58,52|59,54|60,56|61,58|62,60|63,62"&gt;the graph&lt;/a&gt;:&lt;/p&gt;

&lt;p class=centred&gt;&lt;img height=762 src=http://garethrees.org/2013/05/15/euler/problem209.png width=783&gt;&lt;/p&gt;

&lt;p&gt;In order to count the number of truth tables in the original problem, we need to count the ways of two-colouring the nodes of this graph such that no two black nodes are adjacent. The graph is made up of six components, and each component can be coloured independently. So the total number of colourings is the product of the number of colourings of each component. You’ll observe that the graph is cyclic, with cycles of length 1, 2, 3, 6, 6, and 46. So the problem reduces to counting the number of ways there are to two-colour a cycle of length \(n\) such that no two black nodes are adjacent.&lt;/p&gt;

&lt;p&gt;How to do that? Well, suppose we want to pick \(k\) non-adjacent nodes from a cycle of length \(n\). (Where \(0 \le k \le \lfloor {n \over 2} \rfloor\).) Start by placing the \(k\) black nodes with another \(k\) white nodes between them, to make gaps that keep them from being adjacent. The remaining \(n − 2k\) white nodes can be placed in any of the \(k\) gaps: there are \(C(n−k−1, k−1)\) ways to do this. Then multiply by \(n\) (because the first black node may go anywhere in the cycle) and divide by \(k\) (because the black nodes are indistinguishable).&lt;/p&gt;

&lt;pre class=long&gt;&lt;code&gt;&lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;combinations&lt;/span&gt;(n, k):
    &lt;span class=string&gt;"""Return C(n, k), the number of combinations of k out of n."""&lt;/span&gt;
    &lt;span class=variable-name&gt;c&lt;/span&gt; = 1
    &lt;span class=variable-name&gt;k&lt;/span&gt; = &lt;span class=builtin&gt;min&lt;/span&gt;(k, n - k)
    &lt;span class=keyword&gt;for&lt;/span&gt; i &lt;span class=keyword&gt;in&lt;/span&gt; &lt;span class=builtin&gt;range&lt;/span&gt;(1, k + 1):
        &lt;span class=variable-name&gt;c&lt;/span&gt; *= n - k + i
        &lt;span class=variable-name&gt;c&lt;/span&gt; //= i
    &lt;span class=keyword&gt;return&lt;/span&gt; c

&lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;cyclic_nonadjacent_combinations&lt;/span&gt;(n, k):
    &lt;span class=string&gt;"""Return the number of ways to choose k elements from a cycle of
    length n, such that none of the chosen elements are adjacent.

    """&lt;/span&gt;
    &lt;span class=keyword&gt;if&lt;/span&gt; n &amp;lt; 0 &lt;span class=keyword&gt;or&lt;/span&gt; k &amp;lt; 0 &lt;span class=keyword&gt;or&lt;/span&gt; n - k &amp;lt; k: &lt;span class=keyword&gt;return&lt;/span&gt; 0
    &lt;span class=keyword&gt;if&lt;/span&gt; k == 0: &lt;span class=keyword&gt;return&lt;/span&gt; 1
    &lt;span class=keyword&gt;return&lt;/span&gt; n * combinations(n - k - 1, k - 1) // k

&lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;cyclic_nonadjacent_two_colourings&lt;/span&gt;(n):
    &lt;span class=string&gt;"""Return the number of ways to two-colour a cycle of length n such
    that no black nodes are adjacent.

    """&lt;/span&gt;
    &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=builtin&gt;sum&lt;/span&gt;(cyclic_nonadjacent_combinations(n, k) &lt;span class=keyword&gt;for&lt;/span&gt; k &lt;span class=keyword&gt;in&lt;/span&gt; &lt;span class=builtin&gt;range&lt;/span&gt;(n // 2 + 1))&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let’s check that for some small values of \(n\):&lt;/p&gt;

&lt;pre class=long&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; [cyclic_nonadjacent_two_colourings(n) &lt;span class=keyword&gt;for&lt;/span&gt; n &lt;span class=keyword&gt;in&lt;/span&gt; (2, 3, 4)]
[3, 4, 7]&lt;/code&gt;&lt;/pre&gt;

&lt;p class=centred&gt;&lt;img src=http://garethrees.org/2013/05/15/euler/two-colourings.svg&gt;&lt;/p&gt;

&lt;p&gt;This solves the problem in 125 µs:&lt;/p&gt;

&lt;pre class=long&gt;&lt;code&gt;&lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;problem209&lt;/span&gt;():
    &lt;span class=variable-name&gt;c&lt;/span&gt; = cyclic_nonadjacent_two_colourings
    &lt;span class=keyword&gt;return&lt;/span&gt; c(1) * c(2) * c(3) * c(6) * c(6) * c(46)

&amp;gt;&amp;gt;&amp;gt; &lt;span class=keyword&gt;from&lt;/span&gt; timeit &lt;span class=keyword&gt;import&lt;/span&gt; timeit
&amp;gt;&amp;gt;&amp;gt; timeit(problem209)
125.88723683357239&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But is there more to these counts of cyclic nonadjacent two-colourings? Let’s compute a few more values and look them up in the &lt;a href=https://oeis.org/&gt;On-Line Encyclopedia of Integer Sequences&lt;/a&gt;.&lt;/p&gt;

&lt;pre class=long&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; [cyclic_nonadjacent_two_colourings(n) &lt;span class=keyword&gt;for&lt;/span&gt; n &lt;span class=keyword&gt;in&lt;/span&gt; &lt;span class=builtin&gt;range&lt;/span&gt;(1, 16)]
[1, 3, 4, 7, 11, 18, 29, 47, 76, 123, 199, 322, 521, 843, 1364]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;These are are the &lt;a href=http://en.wikipedia.org/wiki/Lucas_numbers&gt;Lucas numbers&lt;/a&gt; starting with 1 and 3 (&lt;a href=https://oeis.org/A000204&gt;A000204&lt;/a&gt;): in particular, each number is the sum of the previous two. This is because we can adopt the following procedure to generate the cycles of length \(n\). We take each cycle of length \(n-1\), cut it at a distinguished edge, and insert a white node. In these cases the neighbours of the inserted node are not both black (since they were adjacent in the smaller cycle). We also take each cycle of length \(n-2\), duplicate a distinguished node, and insert a node of the opposite colour between the duplicates. In these cases either the inserted node is black, or else it’s white with both neighbours black. (Thus no duplicates arise in the procedure.) The figure below shows how this works in the case of \(n = 4\).&lt;/p&gt;

&lt;p class=centred&gt;&lt;img src=http://garethrees.org/2013/05/15/euler/lucas.svg&gt;&lt;/p&gt;
</description>
<pubDate>Wed, 15 May 2013 00:00:00 GMT</pubDate>
<guid>http://garethrees.org/2013/05/15/euler/</guid>
</item>
<item>
<title>Language-oriented programming</title>
<link>http://garethrees.org/2013/05/09/language/</link>
<description>&lt;img class=sidebar height=488 src=http://garethrees.org/2013/05/09/language/warlock.jpg width=300&gt;

&lt;p&gt;There’s a conventional piece of software development wisdom, to the effect that if writing your own programming language is an absurd and wasteful activity, because developing a programming language is a hideous time-sink, and because some existing language is almost certain to satisfy your needs perfectly well.&lt;/p&gt;

&lt;p&gt;There’s a good deal of truth in this advice, in the case where you find yourself trying to develop a &lt;em&gt;general-purpose&lt;/em&gt; programming language, and &lt;em&gt;high performance&lt;/em&gt; is critical. The vast range of functionality needed to be general purpose overwhelms the capacities of a small team, and it is well known that making an optimizing compiler targeting general-purpose hardware requires an investment of many years of effort.&lt;/p&gt;

&lt;p&gt;But if you’re not facing that situation, then I think that writing your own language has a lot to be said for it:&lt;/p&gt;

&lt;ol&gt;

&lt;li&gt;&lt;p&gt;Languages are not just for describing algorithms to computers: they are for communication with other people. Other programmers, of course, but also non-programmers (or less expert programmers) like designers and managers. And your future self, who has forgotten the details of the implementation. If the syntactic form of the language is familiar to its readers, they have a better chance of being able to understand the code and review it for correctness (even if they cannot necessarily &lt;em&gt;write&lt;/em&gt; it).&lt;/p&gt;

&lt;p&gt;For example, in Mathematica, &lt;a href=http://reference.wolfram.com/mathematica/guide/Expressions.html&gt;mathematical expressions are basic objects&lt;/a&gt;: thus, the polynomial &lt;i&gt;x&lt;/i&gt;&lt;sup&gt;2&lt;/sup&gt; + 3&lt;i&gt;x&lt;/i&gt; + 2 is represented by the code:&lt;/p&gt;

&lt;blockquote&gt;&lt;code&gt;x^2 + 3 x + 2&lt;/code&gt;&lt;/blockquote&gt;

&lt;p&gt;Compare, for example, with the &lt;a href=http://docs.scipy.org/doc/numpy/reference/routines.polynomials.classes.html&gt;corresponding representation in Numpy&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;&lt;code&gt;Polynomial([2, 3, 1])&lt;/code&gt;&lt;/blockquote&gt;

&lt;p&gt;In the Mathematica case it’s possible for someone completely unfamiliar with the language to verify that the correct polynomial has been entered, but this is not the case in Numpy: for example, which number is the units coefficient?&lt;a href=http://garethrees.org/2013/05/09/language/#note-1 id=noteref-1&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or consider the meaning of &lt;a href=http://inform7.com/learn/eg/rota/source_20.html&gt;the following snippet of code&lt;/a&gt; in &lt;a href=http://inform7.com/&gt;Inform 7&lt;/a&gt;. Even if you don’t know the first thing about Inform 7, you can probably guess what it means:&lt;/p&gt;

&lt;blockquote&gt;&lt;code&gt;The player is in Longwall Street. The player knows detect trap, memorise, fashion staff, know nature and exorcise undead. The player wears a leather jerkin. The player carries a dagger.&lt;/code&gt;&lt;/blockquote&gt;

&lt;li&gt;&lt;p&gt;Implementing your own language allows you to impose modelling constraints on programs, making it impossible to express certain kinds of common errors, like dereferencing of null pointers, out-of-bounds array access, or the addition of numbers representing measurements in different units.&lt;/p&gt;

&lt;li&gt;&lt;p&gt;You can develop an execution model that’s completely different from the execution model of the language that it’s embedded in. It’s often much clearer and more convenient to be able to express concurrent programs in coroutine style, rather than in the form of state machines embedded in a single-threaded program (even if that’s actually how they are implemented).&lt;/p&gt;

&lt;li&gt;&lt;p&gt;A higher-level language aids portability by abstracting away details of the underlying platform. You can port to new platforms by reimplementing parts of the runtime.&lt;/p&gt;

&lt;li&gt;&lt;p&gt;It would be a shame and a waste to have studied lexers and parsers and code generators without actually going on to make use of it!&lt;/p&gt;

&lt;/ol&gt;

&lt;p&gt;The name &lt;em&gt;&lt;a href=http://en.wikipedia.org/wiki/Language-oriented_programming&gt;language-oriented programming&lt;/a&gt;&lt;/em&gt; is sometimes given to the technique of designing a language as the first step in modelling a problem domain. The term was coined by Martin Ward in his 1994 paper “&lt;a href=http://www.cse.dmu.ac.uk/~mward/martin/papers/middle-out-t.pdf&gt;Language Oriented Programming&lt;/a&gt;”. Ward made some quite expansive claims for the technique:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Our experience with FermaT, and the experiences from other projects, indicate that a system implemented using the language oriented method, as a series of language levels, ends up much smaller than an bottom up or top down implementation of the same system. This is due to the fact that with a problem-specific very high level language, a few lines of code are suffcient to implement highly complex functions. The implementation of the language is also kept small since only those features which are relevant to the particular problem domain need to be implemented.&lt;/p&gt;

&lt;p&gt;The small size of the final system means that the total amount of development work required is reduced, without increasing the complexity of the system, and for the same or higher functionality. This leads to improved maintainability, fewer bugs, and improved adaptability&lt;/blockquote&gt;

&lt;p class=centred&gt;★&lt;/p&gt;

&lt;p&gt;To demonstrate the power of the technique, the program below is written in a language that I’ve just invented but which &lt;em&gt;you most likely already know how to read&lt;/em&gt;, because, like me, in your mispent youth you read &lt;a href=http://en.wikipedia.org/wiki/The_Warlock_of_Firetop_Mountain&gt;&lt;cite&gt;The Warlock of Firetop Mountain&lt;/cite&gt;&lt;/a&gt; and similar choose-your-own-adventure books.&lt;/p&gt;


&lt;pre class=long&gt;                                    &lt;b&gt;PAGE 1&lt;/b&gt;

It is only the autumn night that makes you shiver, you tell yourself, but you
know better.

The castle stands on the hill in the gathering dusk. Wrapped in your grey cloak
against the cold, you crouch on the edge of the counterscarp. Eyes peer down
from the battlements, but you need not fear discovery: they are only the empty
sockets in the skulls of the adventurers who have come before you to harrow the
fortress of the wizard Gwydion.

Soon it will be dark enough to make your move.

You have boots.

You have a cloak.

You have a dagger.

To scale the wall, &lt;i&gt;turn to page 27&lt;/i&gt;.

To swim the moat, &lt;i&gt;turn to page 24&lt;/i&gt;.


                                    &lt;b&gt;PAGE 2&lt;/b&gt;

The dagger makes no impression on the chain. “Ah, you wish to free me,” says
the monk. “You have a good heart, but no blade forged of iron can cut a chain
forged with magic.”

Suddenly he stands up. “Only Gwidion’s death can undo his magic.  But if you
wish to defeat him, take this.” He selects a tattered scroll from the shelf
above the desk and hands it to you.

Defeat Gwydion? You only meant to rob him. But you nod and take the scroll.

You have a tattered scroll.

To leave the monk and follow the stone passageway, &lt;i&gt;turn to page 13&lt;/i&gt;.


                                    &lt;b&gt;PAGE 3&lt;/b&gt;

You are in a long stone passageway.

To go through the doorway on the right, &lt;i&gt;turn to page 33&lt;/i&gt;.

To follow the passageway, &lt;i&gt;turn to page 13&lt;/i&gt;.


                                    &lt;b&gt;PAGE 4&lt;/b&gt;

You push the door. It opens noiselessly on well-oiled hinges and you peer
through the gap. What you see inside takes your breath away. Golden coins in
heaps! Tapestries in silk and damask! Kingly crowns studded with polished
stones. This is what you came for.

To fill your pockets and make a run for it, &lt;i&gt;turn to page 34&lt;/i&gt;.

To try the other door instead, &lt;i&gt;turn to page 10&lt;/i&gt;.


                                    &lt;b&gt;PAGE 5&lt;/b&gt;

To tag onto the back of the troop, &lt;i&gt;turn to page 6&lt;/i&gt;.

To stay hidden until they pass, and then try your key in the door of the keep,
&lt;i&gt;turn to page 40&lt;/i&gt;.


                                    &lt;b&gt;PAGE 6&lt;/b&gt;

You follow the troop, imitating their shambolic walk as best you can. No one
seems to notice your presence. The door to the keep swings open, squealing on
its rusty hinges like a stuck pig, and you pass through one by one. The massive
door swings shut behind you.

If you have a cloak, &lt;i&gt;turn to page 36&lt;/i&gt;.

Test your luck. If you succeed, &lt;i&gt;turn to page 23&lt;/i&gt;.

Otherwise, &lt;i&gt;turn to page 21&lt;/i&gt;.


                                    &lt;b&gt;PAGE 7&lt;/b&gt;

The courtyard is quiet in the moonlight, but you remain on your guard. Who
knows what traps Gwydion has set for unwary intruders?

To approach the keep, &lt;i&gt;turn to page 41&lt;/i&gt;.


                                    &lt;b&gt;PAGE 8&lt;/b&gt;

Your boots echo loudly on the tiles. Too loud. You freeze, but the sound of
footsteps does not stop. You turn to see an armoured knight step emerging from
an alcove. Its visor is down, but somehow you doubt that there is a face behind
it.

        suit of armour (the, its) SKILL 10 STAMINA 10

If you win, &lt;i&gt;turn to page 30&lt;/i&gt;.


                                    &lt;b&gt;PAGE 9&lt;/b&gt;

It looks as if the goblin dropped a key in the struggle. You pick it up.

You gained a key.

To leave the scullery by the door, &lt;i&gt;turn to page 7&lt;/i&gt;.


                                    &lt;b&gt;PAGE 10&lt;/b&gt;

You push the door. It opens noiselessly on well-oiled hinges and you peer
through the gap. Inside is a study lined with tapestries, and at a desk sits a
tall white-haired man, bent in concentration over a grimoire. It is the wizard
Gwydion. His staff rests at his side.

If you have a tattered scroll, &lt;i&gt;turn to page 47&lt;/i&gt;.

To attack Gwydion, &lt;i&gt;turn to page 45&lt;/i&gt;.

To try the other door instead, &lt;i&gt;turn to page 4&lt;/i&gt;.


                                    &lt;b&gt;PAGE 11&lt;/b&gt;

You leap through the window.

Test your luck. If you succeed, &lt;i&gt;turn to page 12&lt;/i&gt;.

Otherwise, &lt;i&gt;turn to page 32&lt;/i&gt;.


                                    &lt;b&gt;PAGE 12&lt;/b&gt;

It is a long fall, but the moat is deep. You pull yourself out and slink away
empty-handed into the night. Is that distant laughter you can hear? No
matter. You will be back.


                                    &lt;b&gt;PAGE 13&lt;/b&gt;

The dark passageway enters a large hall lit by a candelabra. Suits of armour
stand silently in alcoves. The floor is tiled in a black and white checkerboard
pattern, and at the far side a wide staircase ascends. Something about the room
makes you suspicious.

To cross the hall, stepping only on the white tiles, &lt;i&gt;turn to page 20&lt;/i&gt;.

To cross the hall, stepping only on the black tiles, &lt;i&gt;turn to page 20&lt;/i&gt;.


                                    &lt;b&gt;PAGE 14&lt;/b&gt;

You dart into the room and snatch the staff from the wizard’s side. He snaps
awake, and makes a grab for it, but you draw back from his reach.

“Very good, {player.name}!” he says. “But with or without my staff, I am still
the wizard Gwydion!”

To fight him, &lt;i&gt;turn to page 29&lt;/i&gt;.

To run away, &lt;i&gt;turn to page 25&lt;/i&gt;.


                                    &lt;b&gt;PAGE 15&lt;/b&gt;

Two steps are all it takes to cover the distance, and then your dagger goes in
between his cervical vertebrae. The monk barely make a sound as he collapses
forward onto the desk, bleeding onto his half-copied page.

You search his cassock efficiently but find nothing. It is only then that you
notice the heavy iron chain from his leg to the desk. He was a prisoner here,
not an enemy.

To go back and follow the stone passageway, &lt;i&gt;turn to page 13&lt;/i&gt;.


                                    &lt;b&gt;PAGE 16&lt;/b&gt;

You place your foot on a loose cobble and it gives way. For a moment you hang
from your fingertips, but with lightning speed you find a new foothold. Soon
you swing up through a machicolation and onto the parapet walk.

To quickly descend the stairs to the courtyard, &lt;i&gt;turn to page 37&lt;/i&gt;.


                                    &lt;b&gt;PAGE 17&lt;/b&gt;

You take a ladleful and sip. Pfaughh! This isn’t soup, it’s laundry! There are
underclothes boiling here… and not overly clean ones.

A scraping from behind you makes you drop the ladle in alarm.  Someone is
coming through the door.

To try to sneak out past the newcomer, &lt;i&gt;turn to page 22&lt;/i&gt;.

To stand your ground, &lt;i&gt;turn to page 26&lt;/i&gt;.

To hide, &lt;i&gt;turn to page 49&lt;/i&gt;.


                                    &lt;b&gt;PAGE 18&lt;/b&gt;

You cough. The monk turns his head. “It’s nearly done,” he says, “I’m writing
as fast as I can. But with this light…” He turns back to his work. It is only
then you notice the heavy iron chain linking his leg to the desk. He is a
prisoner here!

To cut the chain with your dagger, &lt;i&gt;turn to page 2&lt;/i&gt;.

To leave the monk to his fate and follow the stone passageway, &lt;i&gt;turn to page 13&lt;/i&gt;.


                                    &lt;b&gt;PAGE 19&lt;/b&gt;

You place your foot on a loose cobble and it gives way. For a moment you hang
from your fingertips, cursing the mason. And then you are gone.


                                    &lt;b&gt;PAGE 20&lt;/b&gt;

If you have boots, &lt;i&gt;turn to page 8&lt;/i&gt;.

Otherwise, &lt;i&gt;turn to page 30&lt;/i&gt;.


                                    &lt;b&gt;PAGE 21&lt;/b&gt;

You are still wet with water from your swim, and in the silence a drip echoes
in the stone passageway. The three shambling figures turn to look at you. You
wish that you had never seen the rotting flesh beneath their hoods.

          first zombie (the, its) SKILL 4 STAMINA 6

The second zombie is even more hideous than the first. It reaches for you with
the claws on its one good hand.

         second zombie (the, its) SKILL 5 STAMINA 6

The third zombie is more horrible than the first two put together.  It glares
at you with one eye dangling from its socket.

         third zombie (the, its) SKILL 6 STAMINA 6

If you win, &lt;i&gt;turn to page 3&lt;/i&gt;.


                                    &lt;b&gt;PAGE 22&lt;/b&gt;

Test your luck. If you succeed, &lt;i&gt;turn to page 42&lt;/i&gt;.

Otherwise, &lt;i&gt;turn to page 26&lt;/i&gt;.


                                    &lt;b&gt;PAGE 23&lt;/b&gt;

The troop continues down a long stone passageway, but you scuttle through a
doorway on the right, glad to be rid of their unsettling company.

&lt;i&gt;Turn to page 33&lt;/i&gt;.


                                    &lt;b&gt;PAGE 24&lt;/b&gt;

You wrap your boots in your cloak and slip silently into the water. There is a
water gate, barred with iron, but the iron is rusted and crumbling below the
waterline. You take a deep breath and squeeze through into a dark underwater
passage.

Test your stamina. If you succeed, &lt;i&gt;turn to page 35&lt;/i&gt;.

Otherwise, &lt;i&gt;turn to page 46&lt;/i&gt;.


                                    &lt;b&gt;PAGE 25&lt;/b&gt;

Clutching the wizard’s staff, you run out onto the landing.  Gwydion
follows. You run down the stairs. Gwydion follows. You run across the tiled
floor. Gwydion follows, his hobnailed boots ringing on the stone.

A suit of armour steps down from an alcove and swings its sword at Gwydion. He
shatters it with a blast of lightning, but a second suit of armour is at the
wizard’s back, swinging its sword. A third and a fourth step into the fray
until you can no longer see the wizard. Caught in his own trap!

&lt;i&gt;Turn to page 50&lt;/i&gt;.


                                    &lt;b&gt;PAGE 26&lt;/b&gt;

The newcomer is green-skinned and ugly as sin. He lunges at you with a
staff. You have no choice but to defend yourself.

            goblin (the, his) SKILL 7 STAMINA 6

If you win, &lt;i&gt;turn to page 9&lt;/i&gt;.


                                    &lt;b&gt;PAGE 27&lt;/b&gt;

The masonry has not been pointed in many years, and the stones are rough. You
climb swiftly and silently: an owl, perched in an arrow slit, is not disturbed
as you pass. But the wall is high.

Test your skill. If you succeed, &lt;i&gt;turn to page 16&lt;/i&gt;.

Otherwise, &lt;i&gt;turn to page 19&lt;/i&gt;.


                                    &lt;b&gt;PAGE 28&lt;/b&gt;

You flourish your dagger, but Gwydion taps you with his staff and you find
yourself unable to move.

&lt;i&gt;Turn to page 44&lt;/i&gt;.


                                    &lt;b&gt;PAGE 29&lt;/b&gt;

Gwydion raises his hands, lightning flickering from his fingers.

            Gwydion (-, his) SKILL 10 STAMINA 12

If you win, &lt;i&gt;turn to page 50&lt;/i&gt;.


                                    &lt;b&gt;PAGE 30&lt;/b&gt;

You cross the hall and ascend the stairs. On the landing there are two
doors. One has a stuffed owl on the lintel, the other a lizard in a jar.

To enter the door with the stuffed owl, &lt;i&gt;turn to page 4&lt;/i&gt;.

To enter the door with the lizard, &lt;i&gt;turn to page 10&lt;/i&gt;.


                                    &lt;b&gt;PAGE 31&lt;/b&gt;

This page intentionally left blank.


                                    &lt;b&gt;PAGE 32&lt;/b&gt;

It is a long fall, and the ground is hard.


                                    &lt;b&gt;PAGE 33&lt;/b&gt;

This is a scriptorium, with shelves stacked haphazardly with scrolls and
codices. By the light of a candle, a tonsured man sits at a writing desk,
copying a manuscript. His back is turned to you and he does not appear to have
heard him enter.

To slip back out again and follow the stone passageway, &lt;i&gt;turn to page 13&lt;/i&gt;.

To kill the monk, &lt;i&gt;turn to page 15&lt;/i&gt;.

To talk to the monk, &lt;i&gt;turn to page 18&lt;/i&gt;.


                                    &lt;b&gt;PAGE 34&lt;/b&gt;

Just one of the crowns would set you up for life. But you close your fingers on
it and it vanishes.

You hear a mocking laugh behind you, and whirl around to see the wizard Gwydion
standing there.

“Your head will make a fair adornment for my battlements, {player.name},” he
says.

If you have a tattered scroll, &lt;i&gt;turn to page 48&lt;/i&gt;.

To jump out of the window, &lt;i&gt;turn to page 11&lt;/i&gt;.

To attack Gwydion, &lt;i&gt;turn to page 28&lt;/i&gt;.


                                    &lt;b&gt;PAGE 35&lt;/b&gt;

You feel your way along the passage in the dark. Nothing but cold stone. Your
lungs are bursting: you must find a way out or drown.  With a last desperate
burst of energy you kick upwards and surface. Blessed air! You breathe it deep
into your lungs. You seem to have left your cloak and boots in the passage, but
at least you are alive.

You lost your boots.

You lost your cloak.

To haul yourself out of the water and look around, &lt;i&gt;turn to page 38&lt;/i&gt;.


                                    &lt;b&gt;PAGE 36&lt;/b&gt;

Are you scrutinized by unseen eyes as you pass the threshold? With your grey
cloak pulled over your head, you cannot tell.

Your luck went up by 1.

&lt;i&gt;Turn to page 23&lt;/i&gt;.


                                    &lt;b&gt;PAGE 37&lt;/b&gt;

The courtyard is quiet in the moonlight, but you remain on your guard. Who
knows what traps Gwydion has set for unwary intruders?

To enter a low stone building in a corner of the curtain wall, &lt;i&gt;turn to page 38&lt;/i&gt;.

To approach the keep, &lt;i&gt;turn to page 41&lt;/i&gt;.


                                    &lt;b&gt;PAGE 38&lt;/b&gt;

By the flickering light of a fire, you can see that this is a scullery. Buckets
of dirty dishes stand by the water’s edge, and a giant cauldron bubbles over
the flame. There is a door in the west wall.

To drink from the cauldron, &lt;i&gt;turn to page 17&lt;/i&gt;.

To leave the scullery by the door, &lt;i&gt;turn to page 7&lt;/i&gt;.


                                    &lt;b&gt;PAGE 39&lt;/b&gt;

You start to unroll the scroll, but Gwydion dashes it out of your hand with his
staff.

&lt;i&gt;Turn to page 28&lt;/i&gt;.


                                    &lt;b&gt;PAGE 40&lt;/b&gt;

The unsettling shambolic figures disappear into the keep and the door swings
shut behind them. You stay hidden for a quarter of an hour. Nothing moves. You
step silently across the drawbridge and approach the door.

The key grates in the lock but turns. You pull on the massive brass ring, and
the door opens, squealing on its rusty hinges like a stuck pig. You freeze, but
no one seems to have heard: or maybe they are used to the noise. You step
through and leave the door ajar.

&lt;i&gt;Turn to page 3&lt;/i&gt;.


                                    &lt;b&gt;PAGE 41&lt;/b&gt;

Massive blocks of well-fitted grey stone make up the walls of the keep: you can
see no way to scale the walls. Nor is there a moat with unguarded water gate:
just a ditch filled with chevaux de frise.

But what’s this? You crouch behind a pigsty as a troop of guards marches
towards the keep. Though maybe “marches” is the wrong word: the first one is
limping, the second has a hunched back, and the third drags his foot along the
ground.

If you have a key, &lt;i&gt;turn to page 5&lt;/i&gt;.

This could be your only chance. To tag onto the back of the troop, turn to page
6.


                                    &lt;b&gt;PAGE 42&lt;/b&gt;

You flatten yourself against the wall beside the door, and hold your breath as
the newcomer enters. He is green-skinned, ugly as sin, and carrying a staff. He
approaches the cauldron and gives it a stir, sniffing the fumes.

To sneak out while his back is turned, &lt;i&gt;turn to page 7&lt;/i&gt;.


                                    &lt;b&gt;PAGE 43&lt;/b&gt;

Quietly, so as not to disturb the wizard, you unroll the scroll and whisper the
words. Gradually the wizard’s head nods forward until his beard is on the desk
and you can hear him snoring.

To run in and stab him, &lt;i&gt;turn to page 45&lt;/i&gt;.

To steal his staff, &lt;i&gt;turn to page 14&lt;/i&gt;.


                                    &lt;b&gt;PAGE 44&lt;/b&gt;

The wizard studies your face as you stand there paralyzed. “A most respectable
visage. I think it will look best on the western wall.”


                                    &lt;b&gt;PAGE 45&lt;/b&gt;

Two swift steps and you plunge your dagger into his back, only for it to
shatter into pieces. Gwydion jumps up from the desk with staff in hand and taps
you with it. Suddenly you find yourself unable to move.

&lt;i&gt;Turn to page 44&lt;/i&gt;.


                                    &lt;b&gt;PAGE 46&lt;/b&gt;

You feel your way along the passage in the dark. Nothing but cold stone as far
as you can swim. Your lungs are bursting: you must turn back or drown. Back to
the water gate. Where is the gap? In panic you wrench at the bars, but it is
too late….


                                    &lt;b&gt;PAGE 47&lt;/b&gt;

To read your scroll, &lt;i&gt;turn to page 43&lt;/i&gt;.

To attack Gwydion, &lt;i&gt;turn to page 45&lt;/i&gt;.

To try the other door instead, &lt;i&gt;turn to page 4&lt;/i&gt;.


                                    &lt;b&gt;PAGE 48&lt;/b&gt;

To read your scroll, &lt;i&gt;turn to page 39&lt;/i&gt;.

To jump out of the window, &lt;i&gt;turn to page 11&lt;/i&gt;.

To attack Gwydion, &lt;i&gt;turn to page 28&lt;/i&gt;.


                                    &lt;b&gt;PAGE 49&lt;/b&gt;

You crouch down behind the cauldron and watch as a goblin enters the laundry
room. He is green-skinned, ugly as sin, and carrying a staff. He approaches the
cauldron and gives it a stir, sniffing the fumes.

You leap up from your hiding place and push the goblin into the cauldron, where
he expires horribly in the boiling water and a tangle of dirty clothing.

&lt;i&gt;Turn to page 9&lt;/i&gt;.


                                    &lt;b&gt;PAGE 50&lt;/b&gt;

Gwydion is dead. His staff and his books are in your hands. All you have to do
now is find his gold, subdue his minions, free his slaves, and get the treasure
safely away. It should be easy now…
&lt;/pre&gt;

&lt;p&gt;And here's an interpreter for this language, written in Python.&lt;/p&gt;

&lt;pre class=long&gt;&lt;code&gt;&lt;span class=comment-delimiter&gt;# &lt;/span&gt;&lt;span class=comment&gt;-*- coding: utf-8 -*-
&lt;/span&gt;
&lt;span class=keyword&gt;import&lt;/span&gt; re
&lt;span class=keyword&gt;import&lt;/span&gt; random
&lt;span class=keyword&gt;import&lt;/span&gt; sys
&lt;span class=keyword&gt;import&lt;/span&gt; textwrap

&lt;span class=comment-delimiter&gt;# &lt;/span&gt;&lt;span class=comment&gt;For compatibility between Python 2 &amp;amp; 3.
&lt;/span&gt;&lt;span class=keyword&gt;if&lt;/span&gt; sys.hexversion &amp;gt; 0x03000000:
    &lt;span class=builtin&gt;raw_input&lt;/span&gt; = &lt;span class=builtin&gt;input&lt;/span&gt;

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;GameOver&lt;/span&gt;(&lt;span class=type&gt;Exception&lt;/span&gt;):
    &lt;span class=keyword&gt;pass&lt;/span&gt;

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;Roll&lt;/span&gt;(&lt;span class=builtin&gt;object&lt;/span&gt;):
    &lt;span class=string&gt;"""The object `Roll('2d6+1)` represents a roll of 2 6-sided dice,
    plus 1. When converted to a string it describes the roll in
    English.

    """&lt;/span&gt;
    &lt;span class=variable-name&gt;DICE_RE&lt;/span&gt; = re.&lt;span class=builtin&gt;compile&lt;/span&gt;(r&lt;span class=string&gt;'([1-9]\d*)d([1-9]\d*)([+-]\d+)?$'&lt;/span&gt;)

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;__init__&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, dice):
        &lt;span class=variable-name&gt;m&lt;/span&gt; = &lt;span class=keyword&gt;self&lt;/span&gt;.DICE_RE.match(dice)
        &lt;span class=keyword&gt;if&lt;/span&gt; &lt;span class=keyword&gt;not&lt;/span&gt; m: &lt;span class=keyword&gt;raise&lt;/span&gt; &lt;span class=type&gt;ValueError&lt;/span&gt;(&lt;span class=string&gt;'bad dice description: {}'&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(dice))
        &lt;span class=variable-name&gt;n&lt;/span&gt;, &lt;span class=variable-name&gt;k&lt;/span&gt;, &lt;span class=variable-name&gt;bonus&lt;/span&gt; = &lt;span class=builtin&gt;int&lt;/span&gt;(m.group(1)), &lt;span class=builtin&gt;int&lt;/span&gt;(m.group(2)), &lt;span class=builtin&gt;int&lt;/span&gt;(m.group(3) &lt;span class=keyword&gt;or&lt;/span&gt; &lt;span class=string&gt;'0'&lt;/span&gt;)
        &lt;span class=keyword&gt;self&lt;/span&gt;.roll = [random.randint(1, k) &lt;span class=keyword&gt;for&lt;/span&gt; _ &lt;span class=keyword&gt;in&lt;/span&gt; &lt;span class=builtin&gt;range&lt;/span&gt;(n)]
        &lt;span class=keyword&gt;self&lt;/span&gt;.bonus = bonus
        &lt;span class=keyword&gt;self&lt;/span&gt;.total = &lt;span class=builtin&gt;sum&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.roll) + &lt;span class=keyword&gt;self&lt;/span&gt;.bonus

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;__str__&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;if&lt;/span&gt; &lt;span class=builtin&gt;len&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.roll) == 1:
            &lt;span class=variable-name&gt;s&lt;/span&gt; = &lt;span class=string&gt;'{}'&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.roll[0])
        &lt;span class=keyword&gt;elif&lt;/span&gt; &lt;span class=builtin&gt;len&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.roll) == 2:
            &lt;span class=variable-name&gt;s&lt;/span&gt; = &lt;span class=string&gt;'{} and {}'&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(*&lt;span class=keyword&gt;self&lt;/span&gt;.roll)
        &lt;span class=keyword&gt;else&lt;/span&gt;:
            &lt;span class=variable-name&gt;s&lt;/span&gt; = &lt;span class=string&gt;'{}, and {}'&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(&lt;span class=string&gt;', '&lt;/span&gt;.join(&lt;span class=keyword&gt;self&lt;/span&gt;.roll[:-1]), &lt;span class=keyword&gt;self&lt;/span&gt;.roll[-1])
        &lt;span class=keyword&gt;if&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.bonus:
            &lt;span class=variable-name&gt;s&lt;/span&gt; += &lt;span class=string&gt;', plus {}'&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.bonus)
        &lt;span class=keyword&gt;if&lt;/span&gt; &lt;span class=builtin&gt;len&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.roll) &amp;gt; 1 &lt;span class=keyword&gt;or&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.bonus:
            &lt;span class=variable-name&gt;s&lt;/span&gt; += &lt;span class=string&gt;', making {}'&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.total)
        &lt;span class=keyword&gt;return&lt;/span&gt; s

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;Player&lt;/span&gt;(&lt;span class=builtin&gt;object&lt;/span&gt;):
    &lt;span class=variable-name&gt;STATS&lt;/span&gt; = [(&lt;span class=string&gt;'skill'&lt;/span&gt;, &lt;span class=string&gt;'1d6+6'&lt;/span&gt;), (&lt;span class=string&gt;'stamina'&lt;/span&gt;, &lt;span class=string&gt;'1d6+6'&lt;/span&gt;), (&lt;span class=string&gt;'luck'&lt;/span&gt;, &lt;span class=string&gt;'1d6+6'&lt;/span&gt;)]

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;__init__&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, game):
        &lt;span class=keyword&gt;self&lt;/span&gt;.game = game
        &lt;span class=keyword&gt;self&lt;/span&gt;.name = &lt;span class=keyword&gt;self&lt;/span&gt;.game.&lt;span class=builtin&gt;input&lt;/span&gt;(&lt;span class=string&gt;"What is your name? "&lt;/span&gt;)
        &lt;span class=keyword&gt;self&lt;/span&gt;.equipment = &lt;span class=builtin&gt;set&lt;/span&gt;()
        &lt;span class=keyword&gt;self&lt;/span&gt;.initial_stats = &lt;span class=builtin&gt;dict&lt;/span&gt;()
        &lt;span class=keyword&gt;self&lt;/span&gt;.w = &lt;span class=keyword&gt;lambda&lt;/span&gt; s: game.write(s, 4)

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;rollup&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;self&lt;/span&gt;.w(&lt;span class=string&gt;"Welcome, {player.name}. Let’s roll up your character."&lt;/span&gt;)
        &lt;span class=keyword&gt;for&lt;/span&gt; stat, dice &lt;span class=keyword&gt;in&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.STATS:
            &lt;span class=variable-name&gt;r&lt;/span&gt; = Roll(dice)
            &lt;span class=keyword&gt;self&lt;/span&gt;.initial_stats[stat] = r.total
            &lt;span class=builtin&gt;setattr&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, stat, r.total)
            &lt;span class=keyword&gt;self&lt;/span&gt;.w(&lt;span class=string&gt;"{}: you rolled {}."&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(stat.capitalize(), r))

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;test_stat&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, stat):
        &lt;span class=variable-name&gt;w&lt;/span&gt; = &lt;span class=keyword&gt;self&lt;/span&gt;.w
        &lt;span class=variable-name&gt;value&lt;/span&gt; = &lt;span class=builtin&gt;getattr&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, stat)
        w(&lt;span class=string&gt;"Testing your {} ({})."&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(stat, value))
        &lt;span class=variable-name&gt;r&lt;/span&gt; = Roll(&lt;span class=string&gt;'2d6'&lt;/span&gt;)
        &lt;span class=keyword&gt;if&lt;/span&gt; r.total &amp;gt; value:
            w(&lt;span class=string&gt;"You rolled {}. You failed the test."&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(r))
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=constant&gt;False&lt;/span&gt;
        &lt;span class=keyword&gt;elif&lt;/span&gt; r.total == value:
            w(&lt;span class=string&gt;"You rolled {}. You just made it."&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(r))
        &lt;span class=keyword&gt;else&lt;/span&gt;:
            w(&lt;span class=string&gt;"You rolled {}. You passed the test."&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(r))
        &lt;span class=keyword&gt;if&lt;/span&gt; stat == &lt;span class=string&gt;'luck'&lt;/span&gt;:
            &lt;span class=keyword&gt;self&lt;/span&gt;.adjust_stat(stat, -1)
        &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=constant&gt;True&lt;/span&gt;

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;adjust_stat&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, stat, change, report = &lt;span class=constant&gt;True&lt;/span&gt;):
        old_value = &lt;span class=builtin&gt;getattr&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, stat)
        new_value = &lt;span class=builtin&gt;max&lt;/span&gt;(0, &lt;span class=builtin&gt;min&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.initial_stats[stat], old_value + change))
        &lt;span class=variable-name&gt;change&lt;/span&gt; = new_value - old_value
        &lt;span class=builtin&gt;setattr&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, stat, new_value)
        &lt;span class=keyword&gt;if&lt;/span&gt; report &lt;span class=keyword&gt;and&lt;/span&gt; change != 0:
            &lt;span class=keyword&gt;self&lt;/span&gt;.w(&lt;span class=string&gt;"Your {} just went {} by {}. It is now {}."&lt;/span&gt;
                   .&lt;span class=builtin&gt;format&lt;/span&gt;(stat, &lt;span class=string&gt;'down'&lt;/span&gt; &lt;span class=keyword&gt;if&lt;/span&gt; change &amp;lt; 0 &lt;span class=keyword&gt;else&lt;/span&gt; &lt;span class=string&gt;'up'&lt;/span&gt;,
                           &lt;span class=builtin&gt;abs&lt;/span&gt;(change), new_value))

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;Paragraph&lt;/span&gt;(&lt;span class=builtin&gt;object&lt;/span&gt;):
    &lt;span class=string&gt;"""`Paragraph(game, **kwargs)` represents a paragraph of instructions
    on a page. Call it to execute the instructions. The class property
    `re` is a regular expression matching all paragraphs of this type.
    The `kwargs` dictionary passed to the constructor must contain all
    the named groups in `re`.

    """&lt;/span&gt;
    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;__init__&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, game, **kwargs):
        &lt;span class=keyword&gt;self&lt;/span&gt;.game = game
        &lt;span class=keyword&gt;self&lt;/span&gt;.w = &lt;span class=keyword&gt;lambda&lt;/span&gt; s: &lt;span class=keyword&gt;self&lt;/span&gt;.game.write(s, 4)
        &lt;span class=keyword&gt;for&lt;/span&gt; k, v &lt;span class=keyword&gt;in&lt;/span&gt; kwargs.items():
            &lt;span class=builtin&gt;setattr&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, k, v)

    &lt;span class=variable-name&gt;group_re&lt;/span&gt; = re.&lt;span class=builtin&gt;compile&lt;/span&gt;(r&lt;span class=string&gt;'\(?P&amp;lt;(\w+)&amp;gt;'&lt;/span&gt;)

    &lt;span class=type&gt;@classmethod&lt;/span&gt;
    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;group&lt;/span&gt;(cls):
        &lt;span class=string&gt;"""Return the name of the first named group in the `re` property."""&lt;/span&gt;
        &lt;span class=keyword&gt;return&lt;/span&gt; cls.group_re.search(cls.re).group(1)

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;Fight&lt;/span&gt;(Paragraph):
    &lt;span class=variable-name&gt;re&lt;/span&gt; = (r&lt;span class=string&gt;'(?P&amp;lt;monster_name&amp;gt;\S.*\S) \((?P&amp;lt;monster_article&amp;gt;the|an?|-),'&lt;/span&gt;
          r&lt;span class=string&gt;' (?P&amp;lt;monster_pronoun&amp;gt;their|his|her|its)\)'&lt;/span&gt;
          r&lt;span class=string&gt;' +SKILL +(?P&amp;lt;monster_skill&amp;gt;\d+) +STAMINA +(?P&amp;lt;monster_stamina&amp;gt;\d+)'&lt;/span&gt;)

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;__call__&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=variable-name&gt;w&lt;/span&gt; = &lt;span class=keyword&gt;self&lt;/span&gt;.w
        &lt;span class=variable-name&gt;player&lt;/span&gt; = &lt;span class=keyword&gt;self&lt;/span&gt;.game.player
        &lt;span class=variable-name&gt;name&lt;/span&gt; = &lt;span class=keyword&gt;self&lt;/span&gt;.monster_name
        &lt;span class=variable-name&gt;article&lt;/span&gt; = &lt;span class=keyword&gt;self&lt;/span&gt;.monster_article
        &lt;span class=keyword&gt;if&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.monster_article == &lt;span class=string&gt;'-'&lt;/span&gt;:
            fullname = name
        &lt;span class=keyword&gt;else&lt;/span&gt;:
            fullname = &lt;span class=string&gt;'{} {}'&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.monster_article, name)
        &lt;span class=variable-name&gt;pronoun&lt;/span&gt; = &lt;span class=keyword&gt;self&lt;/span&gt;.monster_pronoun
        &lt;span class=variable-name&gt;skill&lt;/span&gt; = &lt;span class=builtin&gt;int&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.monster_skill)
        &lt;span class=variable-name&gt;stamina&lt;/span&gt; = &lt;span class=builtin&gt;int&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.monster_stamina)
        w(&lt;span class=string&gt;'{0.name}: SKILL {0.skill} STAMINA {0.stamina}'&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(player))
        w(&lt;span class=string&gt;'{}: SKILL {} STAMINA {}'&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(name.capitalize(), skill, stamina))
        &lt;span class=keyword&gt;while&lt;/span&gt; &lt;span class=constant&gt;True&lt;/span&gt;:
            &lt;span class=variable-name&gt;player_roll&lt;/span&gt; = Roll(&lt;span class=string&gt;'2d6+{}'&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(player.skill))
            w(&lt;span class=string&gt;"You rolled {}."&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(player_roll))
            &lt;span class=variable-name&gt;monster_roll&lt;/span&gt; = Roll(&lt;span class=string&gt;'2d6+{}'&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(skill))
            w(&lt;span class=string&gt;"{} rolled {}."&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(fullname.capitalize(), monster_roll))
            &lt;span class=keyword&gt;if&lt;/span&gt; monster_roll.total == player_roll.total:
                w(&lt;span class=string&gt;"You skirmish without damage on either side."&lt;/span&gt;)
                &lt;span class=keyword&gt;continue&lt;/span&gt;
            luck_adjustment = 0
            &lt;span class=keyword&gt;if&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.game.&lt;span class=builtin&gt;input&lt;/span&gt;(&lt;span class=string&gt;"Test your luck? "&lt;/span&gt;).lower() &lt;span class=keyword&gt;in&lt;/span&gt; (&lt;span class=string&gt;'y'&lt;/span&gt;, &lt;span class=string&gt;'yes'&lt;/span&gt;):
                luck_adjustment = 1 &lt;span class=keyword&gt;if&lt;/span&gt; player.test_stat(&lt;span class=string&gt;'luck'&lt;/span&gt;) &lt;span class=keyword&gt;else&lt;/span&gt; -1
            &lt;span class=keyword&gt;if&lt;/span&gt; monster_roll.total &amp;gt; player_roll.total:
                player.adjust_stat(&lt;span class=string&gt;'stamina'&lt;/span&gt;, -2 + luck_adjustment, &lt;span class=constant&gt;False&lt;/span&gt;)
                w(&lt;span class=string&gt;"{} hits you! Your stamina is now {}."&lt;/span&gt;
                  .&lt;span class=builtin&gt;format&lt;/span&gt;(fullname.capitalize(), player.stamina))
                &lt;span class=keyword&gt;if&lt;/span&gt; player.stamina &amp;lt;= 0:
                    w(&lt;span class=string&gt;"You die."&lt;/span&gt;)
                    &lt;span class=keyword&gt;raise&lt;/span&gt; GameOver()
            &lt;span class=keyword&gt;else&lt;/span&gt;:
                &lt;span class=variable-name&gt;stamina&lt;/span&gt; = &lt;span class=builtin&gt;max&lt;/span&gt;(0, stamina - (2 ** (luck_adjustment + 1)))
                w(&lt;span class=string&gt;"You hit {}! {} stamina is now {}."&lt;/span&gt;
                  .&lt;span class=builtin&gt;format&lt;/span&gt;(fullname, pronoun.capitalize(), stamina))
                &lt;span class=keyword&gt;if&lt;/span&gt; stamina &amp;lt;= 0:
                    w(&lt;span class=string&gt;"You killed {}."&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(fullname))
                    &lt;span class=keyword&gt;return&lt;/span&gt;

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;NextPage&lt;/span&gt;(Paragraph):
    &lt;span class=variable-name&gt;re&lt;/span&gt; = r&lt;span class=string&gt;'(?:If you win, t|Otherwise, t|T)urn to page (?P&amp;lt;next_page&amp;gt;\S+)\.'&lt;/span&gt;

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;__call__&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.next_page

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;TestEquipment&lt;/span&gt;(Paragraph):
    &lt;span class=variable-name&gt;re&lt;/span&gt; = (r&lt;span class=string&gt;'If you have(?: (?:an?|your|the|some))? (?P&amp;lt;equip_test&amp;gt;\S.*\S),'&lt;/span&gt;
          r&lt;span class=string&gt;' turn to page (?P&amp;lt;equip_page&amp;gt;\S+)\.'&lt;/span&gt;)

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;__call__&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;if&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.equip_test &lt;span class=keyword&gt;in&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.game.player.equipment:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.equip_page

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;TestStat&lt;/span&gt;(Paragraph):
    &lt;span class=variable-name&gt;re&lt;/span&gt; = (r&lt;span class=string&gt;'Test your (?P&amp;lt;test_stat&amp;gt;\S+)\. If you succeed,'&lt;/span&gt;
          r&lt;span class=string&gt;' turn to page (?P&amp;lt;test_page&amp;gt;\S+)\.'&lt;/span&gt;)

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;__call__&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;if&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.game.player.test_stat(&lt;span class=keyword&gt;self&lt;/span&gt;.test_stat):
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.test_page

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;ChangeStat&lt;/span&gt;(Paragraph):
    &lt;span class=variable-name&gt;re&lt;/span&gt; = (r&lt;span class=string&gt;'Your (?P&amp;lt;stat_change&amp;gt;\S+) went (?P&amp;lt;stat_dir&amp;gt;up|down) by'&lt;/span&gt;
          r&lt;span class=string&gt;' (?P&amp;lt;stat_amount&amp;gt;\d+)\.'&lt;/span&gt;)

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;__call__&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=variable-name&gt;amount&lt;/span&gt; = &lt;span class=builtin&gt;int&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.stat_amount) * (-1 &lt;span class=keyword&gt;if&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.stat_dir == &lt;span class=string&gt;'down'&lt;/span&gt; &lt;span class=keyword&gt;else&lt;/span&gt; 1)
        &lt;span class=keyword&gt;self&lt;/span&gt;.game.player.adjust_stat(&lt;span class=keyword&gt;self&lt;/span&gt;.stat_change, amount)

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;GainEquipment&lt;/span&gt;(Paragraph):
    &lt;span class=variable-name&gt;re&lt;/span&gt; = (r&lt;span class=string&gt;'You (?:gained|have)(?: (?:an?|your|the|some))?'&lt;/span&gt;
          r&lt;span class=string&gt;' (?P&amp;lt;gain_equipment&amp;gt;\S.*\S)\.'&lt;/span&gt;)

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;__call__&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;self&lt;/span&gt;.game.player.equipment.add(&lt;span class=keyword&gt;self&lt;/span&gt;.gain_equipment)

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;LoseEquipment&lt;/span&gt;(Paragraph):
    &lt;span class=variable-name&gt;re&lt;/span&gt; = r&lt;span class=string&gt;'You lost(?: (?:an?|your|the|some))? (?P&amp;lt;lose_equipment&amp;gt;\S.*\S)\.'&lt;/span&gt;

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;__call__&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;self&lt;/span&gt;.game.player.equipment.remove(&lt;span class=keyword&gt;self&lt;/span&gt;.lose_equipment)

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;Choice&lt;/span&gt;(Paragraph):
    &lt;span class=variable-name&gt;re&lt;/span&gt; = r&lt;span class=string&gt;'(?P&amp;lt;choice&amp;gt;\S.*\S), turn to page (?P&amp;lt;choice_page&amp;gt;\S+)\.'&lt;/span&gt;

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;__call__&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=variable-name&gt;n_choices&lt;/span&gt; = &lt;span class=builtin&gt;len&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.choices)
        &lt;span class=keyword&gt;if&lt;/span&gt; n_choices == 1:
            &lt;span class=keyword&gt;self&lt;/span&gt;.w(&lt;span class=string&gt;"{}, press return."&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.choices[0][&lt;span class=string&gt;'choice'&lt;/span&gt;]))
        &lt;span class=keyword&gt;else&lt;/span&gt;:
            &lt;span class=keyword&gt;for&lt;/span&gt; i, c &lt;span class=keyword&gt;in&lt;/span&gt; &lt;span class=builtin&gt;enumerate&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.choices):
                &lt;span class=keyword&gt;self&lt;/span&gt;.w(&lt;span class=string&gt;"{}, choose {}."&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(c[&lt;span class=string&gt;'choice'&lt;/span&gt;], i + 1))
        prompt = &lt;span class=string&gt;"? "&lt;/span&gt;
        &lt;span class=keyword&gt;while&lt;/span&gt; &lt;span class=constant&gt;True&lt;/span&gt;:
            inp = &lt;span class=keyword&gt;self&lt;/span&gt;.game.&lt;span class=builtin&gt;input&lt;/span&gt;(prompt)
            &lt;span class=keyword&gt;if&lt;/span&gt; n_choices == 1:
                &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.choices[0][&lt;span class=string&gt;'choice_page'&lt;/span&gt;]
            &lt;span class=keyword&gt;try&lt;/span&gt;:
                choice = &lt;span class=builtin&gt;int&lt;/span&gt;(inp)
                &lt;span class=keyword&gt;if&lt;/span&gt; 1 &amp;lt;= choice &lt;span class=keyword&gt;and&lt;/span&gt; choice &amp;lt;= n_choices:
                    &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.choices[choice - 1][&lt;span class=string&gt;'choice_page'&lt;/span&gt;]
            &lt;span class=keyword&gt;except&lt;/span&gt; &lt;span class=type&gt;ValueError&lt;/span&gt;:
                &lt;span class=keyword&gt;pass&lt;/span&gt;
            prompt = &lt;span class=string&gt;"Please enter a choice from 1 to {}&amp;gt; "&lt;/span&gt;.&lt;span class=builtin&gt;format&lt;/span&gt;(n_choices)

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;Description&lt;/span&gt;(Paragraph):
    re = r&lt;span class=string&gt;'(?P&amp;lt;description&amp;gt;\S.*\S)'&lt;/span&gt;

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;__call__&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;self&lt;/span&gt;.game.write(&lt;span class=keyword&gt;self&lt;/span&gt;.description)

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;Page&lt;/span&gt;(&lt;span class=builtin&gt;object&lt;/span&gt;):
    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;__init__&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, game):
        &lt;span class=keyword&gt;self&lt;/span&gt;.game = game
        &lt;span class=keyword&gt;self&lt;/span&gt;.paras = []
        &lt;span class=keyword&gt;self&lt;/span&gt;.choice = &lt;span class=constant&gt;None&lt;/span&gt;

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;add&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, cls, **kwargs):
        &lt;span class=keyword&gt;if&lt;/span&gt; cls == Choice:
            &lt;span class=keyword&gt;if&lt;/span&gt; &lt;span class=keyword&gt;not&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.choice:
                &lt;span class=keyword&gt;self&lt;/span&gt;.choice = Choice(&lt;span class=keyword&gt;self&lt;/span&gt;.game, choices = [])
                &lt;span class=keyword&gt;self&lt;/span&gt;.paras.append(&lt;span class=keyword&gt;self&lt;/span&gt;.choice)
            &lt;span class=keyword&gt;self&lt;/span&gt;.choice.choices.append(kwargs)
        &lt;span class=keyword&gt;else&lt;/span&gt;:
            &lt;span class=keyword&gt;self&lt;/span&gt;.paras.append(cls(&lt;span class=keyword&gt;self&lt;/span&gt;.game, **kwargs))

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;visit&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;for&lt;/span&gt; para &lt;span class=keyword&gt;in&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.paras:
            page = para()
            &lt;span class=keyword&gt;if&lt;/span&gt; page:
                &lt;span class=keyword&gt;return&lt;/span&gt; page
        &lt;span class=keyword&gt;raise&lt;/span&gt; GameOver()

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;Game&lt;/span&gt;(&lt;span class=builtin&gt;object&lt;/span&gt;):
    width = 72
    para_re = re.&lt;span class=builtin&gt;compile&lt;/span&gt;(
        r&lt;span class=string&gt;'^(?:PAGE (?P&amp;lt;page&amp;gt;\S+)|{})$'&lt;/span&gt;
        .&lt;span class=builtin&gt;format&lt;/span&gt;(&lt;span class=string&gt;'|'&lt;/span&gt;.join(cls.re &lt;span class=keyword&gt;for&lt;/span&gt; cls &lt;span class=keyword&gt;in&lt;/span&gt; Paragraph.__subclasses__())))

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;__init__&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, text):
        &lt;span class=keyword&gt;self&lt;/span&gt;.pages = &lt;span class=builtin&gt;dict&lt;/span&gt;()
        page = &lt;span class=constant&gt;None&lt;/span&gt;
        &lt;span class=keyword&gt;for&lt;/span&gt; para &lt;span class=keyword&gt;in&lt;/span&gt; re.split(r&lt;span class=string&gt;'\n(?:[ \t]*\n)+'&lt;/span&gt;, text.strip()):
            para = textwrap.dedent(para).strip().replace(&lt;span class=string&gt;'\n'&lt;/span&gt;, &lt;span class=string&gt;' '&lt;/span&gt;)
            m = &lt;span class=keyword&gt;self&lt;/span&gt;.para_re.match(para).groupdict()
            &lt;span class=keyword&gt;if&lt;/span&gt; m[&lt;span class=string&gt;'page'&lt;/span&gt;]:
                &lt;span class=keyword&gt;if&lt;/span&gt; page &lt;span class=keyword&gt;is&lt;/span&gt; &lt;span class=constant&gt;None&lt;/span&gt;: &lt;span class=keyword&gt;self&lt;/span&gt;.initial_page = m[&lt;span class=string&gt;'page'&lt;/span&gt;]
                page = &lt;span class=keyword&gt;self&lt;/span&gt;.pages[m[&lt;span class=string&gt;'page'&lt;/span&gt;]] = Page(&lt;span class=keyword&gt;self&lt;/span&gt;)
                &lt;span class=keyword&gt;continue&lt;/span&gt;
            &lt;span class=keyword&gt;for&lt;/span&gt; cls &lt;span class=keyword&gt;in&lt;/span&gt; Paragraph.__subclasses__():
                &lt;span class=keyword&gt;if&lt;/span&gt; m[cls.group()]:
                    page.add(cls, **m)
                    &lt;span class=keyword&gt;break&lt;/span&gt;

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;input&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, prompt):
        answer = &lt;span class=builtin&gt;raw_input&lt;/span&gt;(prompt)
        &lt;span class=keyword&gt;print&lt;/span&gt;(&lt;span class=string&gt;''&lt;/span&gt;)
        &lt;span class=keyword&gt;if&lt;/span&gt; answer.lower() &lt;span class=keyword&gt;in&lt;/span&gt; (&lt;span class=string&gt;'q'&lt;/span&gt;, &lt;span class=string&gt;'quit'&lt;/span&gt;):
            &lt;span class=keyword&gt;raise&lt;/span&gt; GameOver()
        &lt;span class=keyword&gt;return&lt;/span&gt; answer

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;write&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, para, left_margin = 0):
        indent = &lt;span class=string&gt;' '&lt;/span&gt; * left_margin
        &lt;span class=keyword&gt;for&lt;/span&gt; line &lt;span class=keyword&gt;in&lt;/span&gt; textwrap.wrap(para.&lt;span class=builtin&gt;format&lt;/span&gt;(player = &lt;span class=keyword&gt;self&lt;/span&gt;.player),
                                  &lt;span class=keyword&gt;self&lt;/span&gt;.width, initial_indent = indent,
                                  subsequent_indent = indent):
            &lt;span class=keyword&gt;print&lt;/span&gt;(line)
        &lt;span class=keyword&gt;print&lt;/span&gt;(&lt;span class=string&gt;''&lt;/span&gt;)

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;centre&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, text):
        &lt;span class=keyword&gt;self&lt;/span&gt;.write(text, (&lt;span class=keyword&gt;self&lt;/span&gt;.width - &lt;span class=builtin&gt;len&lt;/span&gt;(text)) // 2)

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;play&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;self&lt;/span&gt;.player = Player(&lt;span class=keyword&gt;self&lt;/span&gt;)
        &lt;span class=keyword&gt;self&lt;/span&gt;.player.rollup()
        &lt;span class=keyword&gt;self&lt;/span&gt;.centre(&lt;span class=string&gt;'*'&lt;/span&gt;)
        page = &lt;span class=keyword&gt;self&lt;/span&gt;.initial_page
        &lt;span class=keyword&gt;try&lt;/span&gt;:
            &lt;span class=keyword&gt;while&lt;/span&gt; &lt;span class=constant&gt;True&lt;/span&gt;:
                page = &lt;span class=keyword&gt;self&lt;/span&gt;.pages[page].visit()
        &lt;span class=keyword&gt;except&lt;/span&gt; GameOver:
            &lt;span class=keyword&gt;pass&lt;/span&gt;

&lt;span class=keyword&gt;if&lt;/span&gt; &lt;span class=builtin&gt;__name__&lt;/span&gt; == &lt;span class=string&gt;'__main__'&lt;/span&gt;:
    Game(&lt;span class=builtin&gt;open&lt;/span&gt;(sys.argv[1], encoding=&lt;span class=string&gt;'utf-8'&lt;/span&gt;).read()).play()&lt;/code&gt;&lt;/pre&gt;

&lt;hr&gt;

&lt;ol&gt;

&lt;li id=note-1&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/05/09/language/#noteref-1&gt;^&lt;/a&gt; Thus &lt;a href=http://sympy.org/en/index.html&gt;SymPy&lt;/a&gt; would be a better choice if you needed to manipulate polynomial expressions in Python.&lt;/p&gt;

&lt;/ol&gt;
</description>
<pubDate>Thu, 09 May 2013 00:00:00 GMT</pubDate>
<guid>http://garethrees.org/2013/05/09/language/</guid>
</item>
<item>
<title>Emacs/Perforce integration: back from the dead</title>
<link>http://garethrees.org/2013/05/01/p4.el/</link>
<description>&lt;img class=sidebar src=http://garethrees.org/2013/05/01/p4.el/p4el.svg width=400px&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;p4.el&lt;/code&gt;, the Emacs/Perforce Integration&lt;/strong&gt;, provides an interface to the &lt;a href=http://www.perforce.com/&gt;Perforce software version management system&lt;/a&gt; from the &lt;a href=http://www.gnu.org/software/emacs/&gt;Emacs&lt;/a&gt; text editor. Written originally by Eric Promislow, with important contributions from Rajesh Vaidheeswarran, Peter Österlund, and &lt;a href=http://p4el.sourceforge.net/thanks.html&gt;many others&lt;/a&gt;, it’s a piece of software that I have used more or less every day for the last thirteen years.&lt;/p&gt;

&lt;p&gt;However, the &lt;a href=http://p4el.cvs.sourceforge.net/viewvc/p4el/p4/&gt;SourceForge repository&lt;/a&gt; hasn’t seen any development since 2005&lt;a href=http://garethrees.org/2013/05/01/p4.el/#note-1 id=noteref-1&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;, and it has been looking a bit ragged around the edges for a few years now. Perforce has acquired many useful new features in the last eight years: &lt;a href=http://www.perforce.com/perforce/doc.current/manuals/cmdref/annotate.html&gt;&lt;code&gt;annotate -i&lt;/code&gt;&lt;/a&gt; in 2005.2, &lt;a href=http://www.perforce.com/perforce/doc.current/manuals/cmdref/move.html#1040665&gt;&lt;code&gt;move&lt;/code&gt;&lt;/a&gt; in 2009.1, &lt;a href=http://www.perforce.com/perforce/doc.current/manuals/cmdref/shelve.html#1046440&gt;&lt;code&gt;shelve&lt;/code&gt;&lt;/a&gt; and &lt;a href=http://www.perforce.com/perforce/doc.current/manuals/cmdref/unshelve.html#1046500&gt;&lt;code&gt;unshelve&lt;/code&gt;&lt;/a&gt; in 2009.2, &lt;a href=http://www.perforce.com/perforce/doc.current/manuals/cmdref/grep.html#1040665&gt;&lt;code&gt;grep&lt;/code&gt;&lt;/a&gt; in 2010.1, &lt;a href=http://www.perforce.com/perforce/doc.current/manuals/cmdref/annotate.html&gt;&lt;code&gt;annotate -I&lt;/code&gt;&lt;/a&gt; and &lt;a href=http://www.perforce.com/perforce/doc.current/manuals/cmdref/copy.html#1040665&gt;&lt;code&gt;copy&lt;/code&gt;&lt;/a&gt; in 2010.2, &lt;a href=http://www.perforce.com/perforce/doc.current/manuals/cmdref/sync.html#1040665&gt;&lt;code&gt;sync -s&lt;/code&gt;&lt;/a&gt; and &lt;a href=http://www.perforce.com/perforce/doc.current/manuals/cmdref/streams.html#1048246&gt;&lt;code&gt;streams&lt;/code&gt;&lt;/a&gt; in 2011.1, and &lt;a href=http://www.perforce.com/perforce/doc.current/manuals/cmdref/status.html#1048796&gt;&lt;code&gt;status&lt;/code&gt;&lt;/a&gt;, &lt;a href=http://www.perforce.com/perforce/doc.current/manuals/cmdref/reconcile.html#1048796&gt;&lt;code&gt;reconcile&lt;/code&gt;&lt;/a&gt; and &lt;a href=http://www.perforce.com/perforce/doc.current/manuals/cmdref/populate.html#1048796&gt;&lt;code&gt;populate&lt;/code&gt;&lt;/a&gt; in 2012.1. But more importantly, we’re now making much more serious use of the Internet for software development than we were ten years ago. When &lt;code&gt;p4.el&lt;/code&gt; was first written back in 1996 or so, it was reasonable to assume that the Perforce server was on a local network, and therefore that it would be fine to perform frequent operations, like checking to see if a visited file is under the control of Perforce, synchronously with respect to the Emacs user interface. Sure, Emacs will hang if the Perforce server is unavailable (at least until the network connection times out after thirty seconds or so), but how often does that happen? The Perforce server is very reliable, and hardly ever goes down.&lt;/p&gt;

&lt;p&gt;But nowadays, your Perforce server is on the other side of the world, and you’re accessing it via a VPN from your laptop on the train over a mobile data connection that drops every time the train goes into a tunnel. Even though these circumstances are admittedly somewhat trying for networking applications, it’s still completely unacceptable for Emacs to hang for thirty seconds every time you open a file.&lt;a href=http://garethrees.org/2013/05/01/p4.el/#note-2 id=noteref-2&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So finally I lost patience with this state of affairs, and &lt;a href=https://github.com/gareth-rees/p4.el&gt;I’ve forked &lt;code&gt;p4.el&lt;/code&gt; on github&lt;/a&gt;, and I’m gradually fixing it. At this stage I think it’s too unstable to recommend for everyone, but if you’re a &lt;code&gt;p4.el&lt;/code&gt; user who’s frustrated with the hangs and missing features, and you’re expert enough to cope with it being a bit buggy, then I’d be grateful if you could download a copy, try it out, and report problems (or better still, fork the repository and send me pull requests for your bug fixes). I use GNU Emacs 24.3 on OS X, Windows 7, FreeBSD and Ubuntu, but I don’t propose to put any work into ensuring that &lt;code&gt;p4.el&lt;/code&gt; runs on any other versions of Emacs or any other operating systems. In particular, I’m likely to break XEmacs compatibility unless someone keeps me honest.&lt;/p&gt;

&lt;p class=centred&gt;★&lt;/p&gt;

&lt;p&gt;I’ve made &lt;a href="https://github.com/gareth-rees/p4.el/issues?state=closed"&gt;many improvements to the code&lt;/a&gt;, but the most important are as follows:&lt;/p&gt;

&lt;ol&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=https://github.com/gareth-rees/p4.el/issues/1&gt;You no longer lose unsaved changes in a buffer when you run the &lt;code&gt;p4-edit&lt;/code&gt; command.&lt;/a&gt; Instead, you are prompted as to whether you want to revert the buffer.&lt;/p&gt;

&lt;p&gt;A warning about this bug was right there near the top of &lt;code&gt;p4.el&lt;/code&gt; for at least the last ten years:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;;; WARNING:
;; --------
;;
;;    % p4 edit foo.c
;;    ... make changes to foo.c in emacs
;;    % p4 submit
;;     ... keep the writable copy of foo.c in emacs.  Start making changes
;;     to it.  Discover that you can't save it.  If you do M-x:p4-edit,
;;     you'll lose your changes.  You need to do a 'p4 edit' at the
;;     command-line.
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;This was a horrible misfeature, because in situations where you have the “writable on client” flag turned on your Perforce client workspace, it’s completely natural to start editing the file, and only later remember that you should have opened it for edit. If you forgot to save before running &lt;code&gt;p4-edit&lt;/code&gt;, then you lost your work.&lt;/p&gt;

&lt;li&gt;&lt;p&gt;If you are logged out of Perforce, then nearly all&lt;a href=http://garethrees.org/2013/05/01/p4.el/#note-3 id=noteref-3&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; Emacs commands that run a Perforce command will prompt you to log in, and then &lt;a href=https://github.com/gareth-rees/p4.el/issues/7&gt;automatically re-run the Perforce command that failed&lt;/a&gt;.&lt;/p&gt;

&lt;li&gt;&lt;p&gt;The “mode check”—that is, the check, when you open a file, to see whether that file is under control of Perforce, so that the mode line can be updated, and menu options can be enabled or disabled—now runs quietly (that is, it never bothers you, even if there are errors) and asynchronously with respect to the Emacs user interface. This means that if you are offline, or the Perforce server is otherwise unavailable, then &lt;a href=https://github.com/gareth-rees/p4.el/issues/33&gt;Emacs no longer hangs each time you open a file&lt;/a&gt;, waiting for a network timeout to the Perforce server. And if you are logged out, then &lt;code&gt;p4.el&lt;/code&gt; &lt;a href=https://github.com/gareth-rees/p4.el/issues/34&gt;does not bother you for your Perforce password&lt;/a&gt; just for opening a file.&lt;/p&gt;

&lt;p&gt;(There is a user option, &lt;code&gt;p4-do-find-file&lt;/code&gt;, that disables the mode check. I've left this user option in place because if you don't use the menu, or don't care about the mode line and the menu options being correctly updated, then you can avoid these queries to the Perforce server by disabling the option.)&lt;/p&gt;

&lt;li&gt;&lt;p&gt;Many more user commands now run Perforce in the background.&lt;a href=http://garethrees.org/2013/05/01/p4.el/#note-4 id=noteref-4&gt;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;

&lt;li&gt;&lt;p&gt;When you revert a file with changes, &lt;a href=https://github.com/gareth-rees/p4.el/issues/17&gt;you get shown the diffs that you are about to revert&lt;/a&gt;. (I got this excellent idea from the &lt;code&gt;vc-revert&lt;/code&gt; command.)&lt;/p&gt;

&lt;li&gt;&lt;p&gt;When you submit a changelist that includes a file that is open for edit but with no changes, &lt;code&gt;p4.el&lt;/code&gt; prompts you “File with empty diff opened for edit. Submit anyway?” &lt;a href=https://github.com/gareth-rees/p4.el/issues/11&gt;The file or files in question are now shown to you in a buffer&lt;/a&gt;, to avoid the awkward pause where you have to go away and try to figure out which file or files is being complained about. (This feature also now uses &lt;code&gt;p4 diff -sr&lt;/code&gt; instead of plain &lt;code&gt;p4 diff&lt;/code&gt;, so the check for empty diffs is much faster in situations when you have made many changes.)&lt;/p&gt;

&lt;li&gt;&lt;p&gt;Errors from Perforce commands are shown to the user more reliably. There should be no need any more to switch to the &lt;code&gt;*P4*&lt;/code&gt; buffer to try to figure out why your command didn't work.&lt;/p&gt;

&lt;li&gt;&lt;p&gt;The &lt;code&gt;p4-blame&lt;/code&gt; command &lt;a href=https://github.com/gareth-rees/p4.el/issues/19&gt;now uses &lt;code&gt;p4 annotate -i -c&lt;/code&gt; if the server supports this&lt;/a&gt;, instead of working out the annotations by starting with the base revision, and patching it with each diff. It also caches the annotation strings instead of rebuilding them for each line. This makes it much faster on files with many revisions. For users on older servers the annotate-by-patching code still works, and now there are now progress messages to reassure you that the operation has not hung.&lt;/p&gt;

&lt;/ol&gt;

&lt;hr&gt;

&lt;ol&gt;

&lt;li id=note-1&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/05/01/p4.el/#noteref-1&gt;^&lt;/a&gt; &lt;a href=https://github.com/fujii/p4el&gt;Fujii Hironori&lt;/a&gt; worked on it in 2009, adding major modes for the output buffers and modernizing the syntax highlighting and menus.&lt;/p&gt;

&lt;li id=note-2&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/05/01/p4.el/#noteref-2&gt;^&lt;/a&gt; Sure, you can turn off the user option &lt;code&gt;p4-do-find-file&lt;/code&gt; when you’re on the train and turn it back on again when you get back to the office. Or you can type &lt;kbd&gt;C-g&lt;/kbd&gt; to abort the operation and then revisit the file. But life would be so much better if things like this worked smoothly out of the box without you having to nurse them along like this.&lt;/p&gt;

&lt;li id=note-3&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/05/01/p4.el/#noteref-3&gt;^&lt;/a&gt; The two outstanding exceptions  are &lt;a href=https://github.com/gareth-rees/p4.el/issues/29&gt;committing a form&lt;/a&gt; and &lt;a href=https://github.com/gareth-rees/p4.el/issues/35&gt;&lt;code&gt;p4-resolve&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;li id=note-4&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/05/01/p4.el/#noteref-4&gt;^&lt;/a&gt; Caveat: I’m not entirely certain if it’s wise for &lt;em&gt;all&lt;/em&gt; user operations to run in the background. Initially I thought it was nice to be able to issue a big &lt;code&gt;p4-describe&lt;/code&gt; or whatever and then immediately get on with some other work while the server chugged away processing my query. But then I found myself worrying about whether in fact it had succeeded, and going off to check the relevant buffer to see if it had failed for some reason. This despite having programmed it to always show me error messages, if there are any. So if you try out the new code, let me know what you think about this. Maybe there needs to be some kind of dashboard, if only to reassure you that nothing’s gone wrong?&lt;/p&gt;

&lt;/ol&gt;
</description>
<pubDate>Wed, 01 May 2013 00:00:00 GMT</pubDate>
<guid>http://garethrees.org/2013/05/01/p4.el/</guid>
</item>
<item>
<title>Bogus foreign keys in Django</title>
<link>http://garethrees.org/2013/03/24/foreign/</link>
<description>&lt;p&gt;This is a problem that had me puzzled for a while, but has a really simple solution, so I’m writing it up here in case it might be useful to other developers who find themselves in this slightly bizarre situation.&lt;/p&gt;


&lt;h2&gt;1. The background&lt;/h2&gt;

&lt;img class=sidebar height=125 src=http://garethrees.org/2013/03/24/foreign/thumb.png width=125&gt;

&lt;p&gt;There’s quite a lot of background to get through before I can introduce the problem. Imagine that you are developing a web site using the &lt;a href=https://www.djangoproject.com/&gt;Django framework&lt;/a&gt;. The underlying database has been in production for some years, so that the ideal mode of operation (where you describe your data model to Django and then it creates the appropriate database tables) is not available. Instead you have to reverse-engineer the database and translate it into a form that Django understands. So you have a look at the database and it looks like this:&lt;a href=http://garethrees.org/2013/03/24/foreign/#note-1 id=noteref-1&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;mysql&amp;gt; select * from user limit 3;
+--------+------------------+----------------+
| userid | name             | (other fields) |
+--------+------------------+----------------+
|      1 | Alarico De Luca  | ...            |
|      2 | Blanka Zielinska | ...            |
|      3 | Cristin Åberg    | ...            |
+--------+------------------+----------------+
3 rows in set (0.00 sec)

mysql&amp;gt; select * from task limit 3;
+--------+--------+----------------+
| taskid | userid | (other fields) |
+--------+--------+----------------+
|      1 | 3      | ...            |
|      2 | 1      | ...            |
|      3 | 2      | ...            |
+--------+--------+----------------+
3 rows in set (0.00 sec)
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;So there are tasks and users, and each task is assigned to a user. So let’s translate that into a couple of Django models:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;&lt;span class=keyword&gt;from&lt;/span&gt; django.db &lt;span class=keyword&gt;import&lt;/span&gt; models

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;User&lt;/span&gt;(models.Model):
    id = models.IntegerField(primary_key=&lt;span class=constant&gt;True&lt;/span&gt;, db_column=&lt;span class=string&gt;'userid'&lt;/span&gt;)
    &lt;span class=variable-name&gt;name&lt;/span&gt; = models.CharField()

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;Task&lt;/span&gt;(models.Model):
    id = models.IntegerField(primary_key=&lt;span class=constant&gt;True&lt;/span&gt;, db_column=&lt;span class=string&gt;'taskid'&lt;/span&gt;)
    &lt;span class=variable-name&gt;user&lt;/span&gt; = models.ForeignKey(User, null=&lt;span class=constant&gt;True&lt;/span&gt;, db_column=&lt;span class=string&gt;'userid'&lt;/span&gt;)
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;This all looks good from the Django shell:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;&amp;gt;&amp;gt;&amp;gt; from myapp.models import *
&amp;gt;&amp;gt;&amp;gt; User.objects.values('name')[0]
{'name': u'Alarico De Luca'}
&amp;gt;&amp;gt;&amp;gt; Task.objects.get(id = 1).user.name
u'Cristin Åberg'
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;So you write a template to present some query results in a table:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;&amp;lt;table&amp;gt;
  &amp;lt;thead&amp;gt;
    &amp;lt;tr&amp;gt;
      &amp;lt;td&amp;gt;#&amp;lt;/td&amp;gt;
      &amp;lt;td&amp;gt;Assigned to&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
  &amp;lt;/thead&amp;gt;
  &amp;lt;tbody&amp;gt;
    {% for task in tasks %}
      &amp;lt;tr&amp;gt;
        &amp;lt;td&amp;gt;{{ task.id }}&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;{{ task.user.name }}&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
    {% endfor %}
  &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;But when you run some sample queries, the Django server falls over with the following error while expanding &lt;code&gt;{{ task.user.name }}&lt;/code&gt; in the template:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;Traceback (most recent call last):
  [...]
  File "[...]/django/db/models/fields/__init__.py", line 537, in get_prep_value
    return int(value)
ValueError: invalid literal for int() with base 10: 'John Smith'
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;What’s going on here? You dig into the backtrace and discover that the offending task is number 123.&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;mysql&amp;gt; select * from task where taskid=123;
+--------+------------+----------------+
| taskid | userid     | (other fields) |
+--------+------------+----------------+
|    123 | John Smith | ...            |
+--------+------------+----------------+
1 row in set (0.00 sec)
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;This explains why Django is trying to convert a string into an integer when trying to get the user for this task. The &lt;code&gt;userid&lt;/code&gt; field is a string field! This was in fact deducible from the way the numbers in this field were left-aligned in the output of the query &lt;code&gt;select * from task&lt;/code&gt;, but that was easy to miss. Or it would have been obvious if you had done the sensible thing and looked at the table schema as well as the first few rows:&lt;/p&gt; 

&lt;blockquote&gt;&lt;pre&gt;mysql&amp;gt; describe task;
+----------+--------------+------+-----+---------+----------------+
| Field    | Type         | Null | Key | Default | Extra          |
+----------+--------------+------+-----+---------+----------------+
| taskid   | int(11)      | NO   | PRI | NULL    | auto_increment |
| userid   | varchar(100) | NO   |     | NULL    |                |
| ...      | ...          | ...  | ... | ...     | ...            |
+----------+--------------+------+-----+---------+----------------+
11 rows in set (0.38 sec)
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;You can guess how this might have happened: perhaps originally there was no &lt;code&gt;user&lt;/code&gt; table: instead, the user’s name was written directly into a field in the &lt;code&gt;task&lt;/code&gt; table. Later, the &lt;code&gt;user&lt;/code&gt; table was added, and instead of adding another field to the &lt;code&gt;task&lt;/code&gt; table containing a foreign key on the &lt;code&gt;user&lt;/code&gt; table, the foreign key was placed in the existing &lt;code&gt;userid&lt;/code&gt; field. MySQL is quite happy to join a string field against an integer field (performing an implicit conversion), so it would have been easy for an inexpert or hurried database administrator to make the change this way. Now of course, with several applications deployed against this database, it is not worth the work to fix the schema.&lt;/p&gt;


&lt;h2&gt;2. The problem&lt;/h2&gt;

&lt;p&gt;Right, so now you know that &lt;code&gt;userid&lt;/code&gt; is a string field, but how do you fix the Django model? In particular, if you make this field a &lt;code&gt;models.CharField&lt;/code&gt;, how do you get the associated user? Here’s a sequence of plans that you might try:&lt;/p&gt;

&lt;ol style=list-style-type:upper-alpha&gt;

&lt;li&gt;&lt;p&gt;Leave the field as a &lt;code&gt;models.ForeignKey&lt;/code&gt; field, but add a &lt;code&gt;user()&lt;/code&gt; method that catches the &lt;code&gt;ValueError&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;Task&lt;/span&gt;(models.Model):
    id = models.IntegerField(primary_key=&lt;span class=constant&gt;True&lt;/span&gt;, db_column=&lt;span class=string&gt;'taskid'&lt;/span&gt;)
    &lt;span class=variable-name&gt;userid&lt;/span&gt; = models.ForeignKey(User, null=&lt;span class=constant&gt;True&lt;/span&gt;)

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;user&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;try&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.userid
        &lt;span class=keyword&gt;except&lt;/span&gt; &lt;span class=type&gt;ValueError&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=constant&gt;None&lt;/span&gt;
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;This works, but how do you get at the user name in the exceptional cases where it’s the name (rather than the foreign key) that’s stored in the record’s &lt;code&gt;userid&lt;/code&gt; field?&lt;/p&gt;

&lt;li&gt;&lt;p&gt;Looking at the implementation of foreign key fields in Django (see &lt;a href=https://github.com/django/django/blob/master/django/db/models/fields/related.py&gt;&lt;code&gt;django.db.models.fields.related.py&lt;/code&gt;&lt;/a&gt;), it seems that you can get the actual value of a foreign key field &lt;code&gt;F&lt;/code&gt; (rather than the related object) like this:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;&lt;span class=builtin&gt;getattr&lt;/span&gt;(instance, &lt;span class=builtin&gt;type&lt;/span&gt;(instance).F.field.attname)
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;So you could add a &lt;code&gt;user_name()&lt;/code&gt; method like this:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;Task&lt;/span&gt;(models.Model):
    id = models.IntegerField(primary_key=&lt;span class=constant&gt;True&lt;/span&gt;, db_column=&lt;span class=string&gt;'taskid'&lt;/span&gt;)
    &lt;span class=variable-name&gt;userid&lt;/span&gt; = models.ForeignKey(User, null=&lt;span class=constant&gt;True&lt;/span&gt;)

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;user&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;try&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.userid
        &lt;span class=keyword&gt;except&lt;/span&gt; &lt;span class=type&gt;ValueError&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=constant&gt;None&lt;/span&gt;

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;user_name&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;try&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.userid.name
        &lt;span class=keyword&gt;except&lt;/span&gt; &lt;span class=type&gt;ValueError&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=builtin&gt;getattr&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;, Task.userid.field.attname)
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;The trouble with this is that &lt;code&gt;field.attname&lt;/code&gt; isn’t documented, so it’s not supported. This area of the Django implementation changed significantly between 1.3 and 1.5 (though without breaking &lt;code&gt;field.attname&lt;/code&gt;, as it happens). So this is risky: you would prefer to have a solution that is supported.&lt;/p&gt;

&lt;li&gt;&lt;p&gt;Represent the &lt;code&gt;userid&lt;/code&gt; field in Django as a &lt;code&gt;models.CharField&lt;/code&gt; (so that you can get the name in a supported way), and look up the related object in Python:&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;Task&lt;/span&gt;(models.Model):
    id = models.IntegerField(primary_key=&lt;span class=constant&gt;True&lt;/span&gt;, db_column=&lt;span class=string&gt;'taskid'&lt;/span&gt;)
    &lt;span class=variable-name&gt;userid&lt;/span&gt; = models.CharField()

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;user&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;try&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; User.objects.get(id = &lt;span class=builtin&gt;int&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.userid))
        &lt;span class=keyword&gt;except&lt;/span&gt; &lt;span class=type&gt;ValueError&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=constant&gt;None&lt;/span&gt;
        &lt;span class=keyword&gt;except&lt;/span&gt; User.DoesNotExist:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=constant&gt;None&lt;/span&gt;

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;user_name&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;try&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.user().name
        &lt;span class=keyword&gt;except&lt;/span&gt; &lt;span class=type&gt;AttributeError&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.userid
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;This is a non-starter: it means a whole extra database query each time a user name is looked up, and kills performance. So on to:&lt;/p&gt;

&lt;li&gt;&lt;p&gt;Fetch all the users from the database, store them using &lt;a href=https://docs.djangoproject.com/en/dev/topics/cache/&gt;Django’s cache framework&lt;/a&gt;, and use the cache to look up the user as needed?&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;&lt;span class=keyword&gt;from&lt;/span&gt; django.core.cache &lt;span class=keyword&gt;import&lt;/span&gt; cache

&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;Task&lt;/span&gt;(models.Model):
    id = models.IntegerField(primary_key=&lt;span class=constant&gt;True&lt;/span&gt;, db_column=&lt;span class=string&gt;'taskid'&lt;/span&gt;)
    &lt;span class=variable-name&gt;userid&lt;/span&gt; = models.CharField()

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;user&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=variable-name&gt;users&lt;/span&gt; = cache.get(&lt;span class=string&gt;'users'&lt;/span&gt;)
        &lt;span class=keyword&gt;if&lt;/span&gt; users &lt;span class=keyword&gt;is&lt;/span&gt; &lt;span class=constant&gt;None&lt;/span&gt;:
            &lt;span class=variable-name&gt;users&lt;/span&gt; = {user.id: user &lt;span class=keyword&gt;for&lt;/span&gt; user &lt;span class=keyword&gt;in&lt;/span&gt; User.objects.all()}
            cache.set(&lt;span class=string&gt;'users'&lt;/span&gt;, users)
        &lt;span class=keyword&gt;try&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; users[&lt;span class=builtin&gt;int&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;.userid)]
        &lt;span class=keyword&gt;except&lt;/span&gt; &lt;span class=type&gt;ValueError&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=constant&gt;None&lt;/span&gt;
        &lt;span class=keyword&gt;except&lt;/span&gt; &lt;span class=type&gt;KeyError&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=constant&gt;None&lt;/span&gt;

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;user_name&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;try&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.user().name
        &lt;span class=keyword&gt;except&lt;/span&gt; &lt;span class=type&gt;AttributeError&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.userid
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;However, (a) there are so many users that filling the cache takes tens of seconds which means that the first person to query the site after a reboot is going to suffer an unacceptable delay; and (b) the cache is going to get out of date and need refreshing. Solving both of these is a Simple Matter of Programming, but this is looking over-complicated and error prone. Plan B would surely be better than this!&lt;/p&gt;

&lt;/ol&gt;


&lt;h2&gt;3. The solution&lt;/h2&gt;

&lt;p&gt;You can have two fields in a Django model that refer to the same database column, but with different field types!&lt;/p&gt;

&lt;blockquote&gt;&lt;pre&gt;&lt;span class=keyword&gt;class&lt;/span&gt; &lt;span class=type&gt;Task&lt;/span&gt;(models.Model):
    id = models.IntegerField(primary_key=&lt;span class=constant&gt;True&lt;/span&gt;, db_column=&lt;span class=string&gt;'taskid'&lt;/span&gt;)
    &lt;span class=variable-name&gt;user&lt;/span&gt; = models.ForeignKey(User, null=&lt;span class=constant&gt;True&lt;/span&gt;,&lt;a href=http://garethrees.org/2013/03/24/foreign/#note-2 id=noteref-2&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; db_column=&lt;span class=string&gt;'userid'&lt;/span&gt;)
    &lt;span class=variable-name&gt;userid&lt;/span&gt; = models.CharField()

    &lt;span class=keyword&gt;def&lt;/span&gt; &lt;span class=function-name&gt;user_name&lt;/span&gt;(&lt;span class=keyword&gt;self&lt;/span&gt;):
        &lt;span class=keyword&gt;try&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.user.name
        &lt;span class=keyword&gt;except&lt;/span&gt; &lt;span class=type&gt;ValueError&lt;/span&gt;:
            &lt;span class=keyword&gt;return&lt;/span&gt; &lt;span class=keyword&gt;self&lt;/span&gt;.userid
&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;Obvious when you know how…&lt;/p&gt;

&lt;hr&gt;

&lt;ol&gt;

&lt;li id=note-1&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/03/24/foreign/#noteref-1&gt;^&lt;/a&gt; Made up example.&lt;/p&gt;

&lt;li id=note-2&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/03/24/foreign/#noteref-2&gt;^&lt;/a&gt; One of the effects of setting &lt;code&gt;null=True&lt;/code&gt; for a foreign key is that the &lt;a href=https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.select_related&gt;&lt;code&gt;select_related()&lt;/code&gt;&lt;/a&gt; method “by default, does not follow foreign keys that have &lt;code&gt;null=True&lt;/code&gt;”. You need to request the field explicitly: in this case, by calling &lt;code&gt;select_related('user')&lt;/code&gt;.&lt;/p&gt;

&lt;/ol&gt;
</description>
<pubDate>Sun, 24 Mar 2013 00:00:00 GMT</pubDate>
<guid>http://garethrees.org/2013/03/24/foreign/</guid>
</item>
<item>
<title>CTC ride to Buntingford</title>
<link>http://garethrees.org/2013/03/10/ctc/</link>
<description>&lt;p&gt;I looked out of the window this morning to see snow flakes falling from a dark cloudy sky. It was chilly out: just barely above freezing, and there was a cold north-easterly wind. Just the kind of day on which it would have been nice to sit in a warm house and drink tea. If only I hadn’t promised to the lead day ride.&lt;/p&gt;

&lt;p&gt;The first challenge was to get to Brookside: it was the day of the &lt;a href=http://www.cambridge-news.co.uk/News/Thousands-brave-the-snow-for-Cambridge-Half-Marathon-10032013.htm&gt;Cambridge half-marathon&lt;/a&gt;, and the city centre was wrapped in orange tape and surrounded with race marshals. I made a detour around the blockage via Grange Road and Barton Road, and at Brookside I found three riders: Tom, Neil, and Conrad. A pretty good turnout given the conditions.&lt;/p&gt;

&lt;p&gt;My plan for the day was to ride directly to coffee, getting there a bit early, and then to have a longer detour between coffee and lunch. It worked out pretty well: the north-easterly wind blew us quickly down the B1368 and up the hill to Barkway, where we turned west towards Reed. The snow had been forecast to stop, but as we climbed up into the Hertfordshire hills, it got heavier, and the north-facing fields were covered with white. Never has arriving at the Silver Ball Café seemed so welcome!&lt;/p&gt;

&lt;p class="box jigsaw" style=width:400px&gt;&lt;a href=http://garethrees.org/2013/03/10/ctc/IMG_0674.jpg&gt;&lt;img height=300 src=http://garethrees.org/2013/03/10/ctc/IMG_0674-thumb.jpg width=400&gt;&lt;/a&gt; Near Wood End, Hertfordshire.&lt;/p&gt;

&lt;p&gt;Neil had to turn home, so there were only three of us as we set out into the narrow lanes west of the A10, with the snow still swirling around us. The roads were well above freezing, so the snow was melting rather settling, but many of the minor lanes were in quite shocking condition: mud and potholes and puddles everywhere. I had washed my bike on Saturday, but now it was utterly filthy again, as were my feet and legs. On the road from Ardeley to Great Munden, Conrad punctured: a sharp little flint had penetrated the tyre (he was sensibly riding his winter bike).&lt;/p&gt;

&lt;p class="box jigsaw" style=width:400px&gt;&lt;a href=http://garethrees.org/2013/03/10/ctc/IMG_0676.jpg&gt;&lt;img height=300 src=http://garethrees.org/2013/03/10/ctc/IMG_0676-thumb.jpg width=400&gt;&lt;/a&gt; Fixing a puncture, near Wood End, Hertfordshire.&lt;/p&gt;

&lt;p&gt;Due to this enforced stop, we were a bit late getting to lunch. Since we were passing through Westmill, We looked in at the &lt;a href=http://westmilltearoom.co.uk/&gt;Westmill Tea Room&lt;/a&gt; (in the old post office building) to see if they had a table for us, but they were booked solid. So we rode on a couple of miles to Buntingford and stopped at the excellent &lt;a href=http://www.thebuntingfordcoffeeshop.co.uk/&gt;Buntingford Coffee Shop&lt;/a&gt;. The warmth of the café was as welcome as the toasted sandwich: I hadn’t been able to feel my toes for some time!&lt;/p&gt;

&lt;p&gt;When we left Buntingford at about 14:15 the snow had stopped, but now we were riding directly into the headwind. On the tops of the hills it was pretty tough making progress. We took a direct route through Wyddial, Barkway, Great Chishill, Heydon, Elmdon and over the hill to Ickleton, where we met the afternoon riders. A good day’s ride despite the appalling weather: about &lt;b&gt;67 miles&lt;/b&gt;.&lt;/p&gt;

&lt;p class=box style=width:425px&gt;
&lt;iframe frameborder=0 height=425 marginheight=0 marginwidth=0 scrolling=no src="https://maps.google.co.uk/maps/ms?msa=0&amp;amp;msid=204201713257751721124.0004d795a6f4a1204ef66&amp;amp;ie=UTF8&amp;amp;t=m&amp;amp;ll=52.064312,0.045319&amp;amp;spn=0.358811,0.583649&amp;amp;z=10&amp;amp;output=embed" width=425&gt;&lt;/iframe&gt; View &lt;a href="https://maps.google.co.uk/maps/ms?msa=0&amp;amp;msid=204201713257751721124.0004d795a6f4a1204ef66&amp;amp;ie=UTF8&amp;amp;t=m&amp;amp;ll=52.064312,0.045319&amp;amp;spn=0.358811,0.583649&amp;amp;z=10&amp;amp;source=embed" style="color: blue; text-align: left;"&gt;CTC ride to Reed, Buntingford and Ickleton&lt;/a&gt; in a larger map.&lt;/p&gt;
</description>
<pubDate>Sun, 10 Mar 2013 00:00:00 GMT</pubDate>
<guid>http://garethrees.org/2013/03/10/ctc/</guid>
</item>
<item>
<title>Great North Road</title>
<link>http://garethrees.org/2013/01/15/hamilton/</link>
<description>&lt;img class=sidebar height=458 src=http://garethrees.org/2013/01/15/hamilton/cover.jpg width=300&gt;

&lt;p&gt;The &lt;em&gt;very first sentence&lt;/em&gt; of Peter F. Hamilton’s &lt;a href="http://www.amazon.co.uk/gp/product/B00844Y4UQ/ref=as_li_ss_tl?ie=UTF8&amp;amp;camp=1634&amp;amp;creative=19450&amp;amp;creativeASIN=B00844Y4UQ&amp;amp;linkCode=as2&amp;amp;tag=garethreesorg-21"&gt;&lt;cite&gt;Great North Road&lt;/cite&gt;&lt;/a&gt; (2012) reads:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;As midnight approached, the wild neon colours of the borealis storm came shimmering through the soft snow falling gently across Newcastle upon Tyne.&lt;/blockquote&gt;

&lt;p&gt;How is that possible? Auroras take place high in the sky, in the thermosphere (above 80 km), but if it’s snowing then it must be cloudy in the lower troposphere (stratus clouds have a base around 2 km), so it would seem very unlikely that you could see an aurora “through snow”. Some more explanation is needed, and it’s not provided. So is this just an unlucky slip, or an example of systematic failure to think through the consequences of the settings and events in the novel? Let’s read on and find out.&lt;a href=http://garethrees.org/2013/01/15/hamilton/#note-1 id=noteref-1&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p class=centred&gt;★&lt;/p&gt;

&lt;p&gt;The background to this novel, which includes interplanetary and interstellar travel, is rather derivative of media franchises like &lt;cite&gt;Stargate&lt;/cite&gt;, &lt;cite&gt;Predator&lt;/cite&gt;, and &lt;cite&gt;Aliens&lt;/cite&gt;.&lt;a href=http://garethrees.org/2013/01/15/hamilton/#note-2 id=noteref-2&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; But the cyberpunkish police-procedural scenes in a mid-22nd century Newcastle are quite different in tone from the space operatic scenes, and this down-to-earth milieu reveals the presence of shackles on the imagination: some of the chapters portray limitless technological wish-fulfilment, but then other chapters portray minor aspects of early 21st century middle-class life in the UK as immune from change. This is a world in which there are commercial fusion plants everywhere, but people are still dependent on petrol-powered motor vehicles. A world in which “lightwave ships” cross the solar system at close to the speed of light, but the fastest way to cross Europe is by jet plane. A world in which there is interstellar teleportation, but no-one has managed to solve the problem of how children can get to school when both parents have jobs:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;“You’ll have to take them to school for me,” Sid announced quickly, hoping it would get overlooked in the general morning chaos.&lt;/p&gt;

&lt;p&gt;“No bloody way!” Jacinta exclaimed.&lt;a href=http://garethrees.org/2013/01/15/hamilton/#note-3 id=noteref-3&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; “We agreed. I’ve got a full cardio replacement booked for this morning.”&lt;/blockquote&gt;

&lt;p&gt;A world in which “phase-change materials” act as near-perfect heat stores but the protagonist is unaware that houses can be retrofitted with insulation:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;A pleasant enough home, but its age meant it was never designed for the cold of today’s winters, so it cost a fortune to heat.&lt;/blockquote&gt;

&lt;img class=sidebar height=456 src=http://garethrees.org/2013/01/15/hamilton/cover2.jpg width=300&gt;

&lt;p&gt;A world in which it is established that detective Sid has a computer embedded in his skin, that can play images directly into his retina and sounds directly into his ears, and yet:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;The alarm clock’s sharp buzz dragged Sid awake. He groaned and reached out for the snooze button. […] Sid picked up the clock and held it close to his face—the only way he could make out the glowing green figures.&lt;/blockquote&gt;

&lt;p&gt;The novel is set in 2143,&lt;a href=http://garethrees.org/2013/01/15/hamilton/#note-4 id=noteref-4&gt;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt; in an alternate and more technologically advanced universe (in which human cloning was carried out in 2007), but in the Newcastle sections of the story, there is little evidence for this beyond references to colder winters and hotter summers. For example, a murder victim is found in the water by the Gateshead Millennium Bridge. Now, sea level rise could be as much as 2 m by the beginning of the 22nd century&lt;a href=http://garethrees.org/2013/01/15/hamilton/#note-5 id=noteref-5&gt;&lt;sup&gt;5&lt;/sup&gt;&lt;/a&gt;, and that would surely put the embankments around the Tyne under water, at least at some states of the tide.&lt;a href=http://garethrees.org/2013/01/15/hamilton/#note-6 id=noteref-6&gt;&lt;sup&gt;6&lt;/sup&gt;&lt;/a&gt; So this scene would be an easy opportunity to put in a bit of futuristic backdrop, but the text gives absolutely no indication that anything has changed.&lt;/p&gt;

&lt;p&gt;The novel presents little evidence of social change having occurred since 2012. Young people wear the same kinds of clothes to go out to the same kinds of nightclubs. Toyota still make reliable mid-price motor vehicles. Car parking remains a problem:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Sid left for the city morgue, which was housed in a modern annex next to the glass and steel towers of Arevalo Medical’s Royal Victoria Infirmary. As he drew into the car park next to the city morgue block Sid saw notices proclaiming that parking would be suspended for two months so footings could be sunk for the new oncology clinic. “So where do we park?” he muttered to himself.&lt;/blockquote&gt;

&lt;p&gt;In this scene Sid drove to the morgue from the police station at the junction of Pilgrim Street and Market Street, a distance of &lt;a href="https://maps.google.co.uk/maps?saddr=Market+Street,+Newcastle+upon+Tyne&amp;amp;daddr=The+Royal+Victoria+Infirmary,+Queen+Victoria+Road,+Newcastle+upon+Tyne&amp;amp;sll=54.973371,-1.611401&amp;amp;sspn=0.002639,0.004195&amp;amp;t=m&amp;amp;dirflg=w&amp;amp;mra=ls&amp;amp;z=16"&gt;&lt;em&gt;about a kilometre&lt;/em&gt;&lt;/a&gt;. He learns nothing there that couldn’t have been communicated electronically, but even if he had to go in person, why didn’t he walk? Is this passage supposed to establish his laziness and selfishness?&lt;/p&gt;

&lt;p&gt;Selfishness is a bit of a theme of &lt;cite&gt;Great North Road&lt;/cite&gt;. The majority of the characters in the novel work for government agencies such as the police and the military, but that doesn’t stop them engaging in boilerplate libertarian moans about the dead hand of government bureaucracy and the burden of the taxes that pay their salaries. At the same time, the corporate world is presented as a Hobbesian war of all against all, in which everyone is corrupt, and one of the rewards for the winners is the opportunity to sexually exploit the losers. In fact, the novel is so devoid of sympathetic characters that I felt a bit disappointed that the alien monster failed to kill everyone.&lt;/p&gt;

&lt;hr&gt;

&lt;ol&gt;

&lt;li id=note-1&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/01/15/hamilton/#noteref-1&gt;^&lt;/a&gt; I was given this book for Christmas, and I have to say that having got used to e-books it felt really awkward to lug about a 1,100-page tome that’s nearly as big as my head. I felt a bit embarassed reading it on the Tube.&lt;/p&gt;

&lt;li id=note-2&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/01/15/hamilton/#noteref-2&gt;^&lt;/a&gt; There’s a scene early on where &lt;del&gt;Ripley&lt;/del&gt; Angela, the only survivor of an alien attack, is persuaded to return to the planet in search of the alien monster. Escorted by a gung-ho team of &lt;del&gt;Colonial Marines&lt;/del&gt; HDA Legionnaires, she warns them that their confidence is unwarranted: “You’re on a trip to poke a stick into a monster’s nest. &lt;del&gt;It'll breed. You'll die. Everyone in the fucking company. Will die.&lt;/del&gt; It will kill you. It will kill all of you. You won’t stand a chance.”&lt;/p&gt;

&lt;li id=note-3&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/01/15/hamilton/#noteref-3&gt;^&lt;/a&gt; If you guessed from this passage that Hamilton’s writing suffers from a bit of a case of “&lt;a href=http://tvtropes.org/pmwiki/pmwiki.php/Main/SaidBookism&gt;said-bookism&lt;/a&gt;” then you’d be right.&lt;/p&gt;

&lt;li id=note-4&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/01/15/hamilton/#noteref-4&gt;^&lt;/a&gt; But two references to the action taking place in the “twenty-third century” &lt;!-- on pages 211 and 247 --&gt; suggest that an earlier draft had placed the novel somewhat farther in the future.&lt;/p&gt;

&lt;li id=note-5&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/01/15/hamilton/#noteref-5&gt;^&lt;/a&gt; See &lt;a href=http://www.cpo.noaa.gov/reports/sealevel/NOAA_SLR_r3.pdf&gt;Global Sea Level Rise Scenarios for the United States National Climate Assessment&lt;/a&gt;, especially figure 10, reproduced below. The novel’s “cheap oil” scenario is surely going to push things towards the high end of the range of predictions.&lt;/p&gt;

&lt;img class="display centred" height=326 src=http://garethrees.org/2013/01/15/hamilton/NOAA_scenarios.png width=600&gt;

&lt;li id=note-6&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/01/15/hamilton/#noteref-6&gt;^&lt;/a&gt; Researching this, I was surprised to learn that the Tyne already floods its embankments under rare conditions: for example, &lt;a href=http://www.flickr.com/photos/danielnrobson/6415304497/&gt;this photo&lt;/a&gt; was taken on 2011-11-27 when a storm surge in the North Sea coincided with a spring tide. These events are only going to become more frequent.&lt;/p&gt;

&lt;/ol&gt;
</description>
<pubDate>Tue, 15 Jan 2013 00:00:00 GMT</pubDate>
<guid>http://garethrees.org/2013/01/15/hamilton/</guid>
</item>
<item>
<title>The last lousy bug</title>
<link>http://garethrees.org/2013/01/06/bug/</link>
<description>&lt;p&gt;Inspired by David Jones’ “&lt;a href=http://drj11.wordpress.com/2011/05/11/my-favourite-bugs/&gt;My Favourite Bugs&lt;/a&gt;”, here’s one of my own war stories.&lt;/p&gt;

&lt;p&gt;Console video games are demanding pieces of software to write: in addition to being entertaining, they must be playable by six-year-olds and self-explanatory to adults. They must be able to gracefully handle corrupted save files. They must never run out of memory, be unresponsive to user input, or take longer than 16 ms to draw the next frame. Oh, and you typically only get one chance to get it right: when the game has to go out to manufacture on cartridge or DVD there’s no chance to fix things with a version 1.1.&lt;/p&gt;

&lt;p&gt;But there are compensations. The ratio of testers to programmers is high, and nothing gives you confidence in the quality of your work as much as knowing that thirty people have been trying to make it break for the last week and they have only found one way to make it crash.&lt;/p&gt;

&lt;p&gt;In the early days of testing, the bugs pile up rapidly, and it’s a scramble to keep the number of open reports down to a manageable level, but enough of the bugs are easy to fix that you can do it. The most exhilarating experience is when you’re working with testers who are in a time zone halfway around the world. You arrive at work in the morning to find a pile of bugs has arrived overnight, you work hard all day, and ship out a new release in the early evening, just in time for the testers to start verifying your fixes and finding new bugs.&lt;/p&gt;

&lt;p&gt;After this has been going on for a few weeks, the low-hanging fruit have been plucked, and a handful of serious bugs have bubbled to the surface. These are the bugs that you haven’t been able to reproduce: the intermittent errors and mysterious crashes. And then eventually you come down to the &lt;em&gt;last lousy bug&lt;/em&gt;, the one that you’ve been putting off in the hope that it would go away by itself, and you have to roll up your sleeves and get your hands dirty.&lt;/p&gt;

&lt;p&gt;Bug 142 was especially mysterious because it happened after the program quit running. On this particular console, returning from the game to main menu was implemented by rebooting: a small region of memory survives the reboot and contains instructions for the boot loader telling it which application to run next. (This might sound strange, but it makes a lot of sense from a robustness point of view: applications aren’t required to co-exist, can’t cause each other to crash, and always start from a known machine configuration.) So when the player requests a quit, the game saves its state, and calls the &lt;code&gt;Reboot&lt;/code&gt; function, which writes the necessary instructions to the protected region of memory, and then reboots the console. Except that our testers were reporting that on rare occasions our game was quitting but then crashing in the boot loader (which reported that it had been passed a bogus argument).&lt;/p&gt;

&lt;p&gt;I always try to bear in mind &lt;a href=http://sourceware.org/gdb/current/onlinedocs/gdb/Bug-Reporting.html&gt;Richard Stallman’s advice&lt;/a&gt; from &lt;a href=http://sourceware.org/gdb/current/onlinedocs/gdb/&gt;&lt;cite&gt;Debugging with GDB&lt;/cite&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Often people who encounter a bug spend a lot of time investigating which changes to the input ﬁle will make the bug go away and which changes will not affect it. &lt;i&gt;[That is, trying to find the “envelope” of the bug.—GDR]&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;This is often time consuming and not very useful, because the way we will ﬁnd the bug is by running a single example under the debugger with breakpoints, not by pure deduction from a series of examples. We recommend that you save your time for something else.&lt;/blockquote&gt;

&lt;p&gt;The trouble was that I couldn’t get the bug to manifest itself in the debugger. The testers were running the game on consumer hardware, so there was no way to attach a debugger when they provoked the bug. And whenever I stepped through the relevant sequence of events the correct arguments were always passed to the boot loader. So all I had to go on was the envelope of the bug, and the only salient fact about the envelope was that the bug only manifested itself in the RELEASE build of the game (with all debugging information and assertions stripped); never in the DEBUG build. (Which should have suggested that the problem might have something to do with memory layout, though I didn’t realize this until later.) I checked the documentation for the shutdown sequence and everything looked OK. We faded the screen to black, waited for the graphics pipeline to empty, faded out the music, waited for any pending save operation to finish, shut down any libraries that required it, and finally called &lt;code&gt;Reboot&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So all I could do was to prepare a special RELEASE build with extra diagnostic support, and work on the other bugs, hoping that eventually something would turn up. But the pile of outstanding bugs grew smaller and smaller, and eventually it was clear that bug 142 would be the last lousy bug in the game.&lt;/p&gt;

&lt;p class="box sidebar" style=width:480px&gt;&lt;img alt="CRASH: *** DSI exception ***
PC=800E9D5C
80613AA0: 80613B60 800C8AF0
80613B60: 80613B70 800C8DE8
80613B70: 80613BF0 80097B54
80613BF0: 80613C00 8009FD90
80613C00: 80614020 800A0518
80614020: FFFFFFFF 800041A4" height=360 src=http://garethrees.org/2013/01/06/bug/stack-trace.jpg width=480&gt; The screenshot showing the stack trace that unlocked the puzzle.&lt;/p&gt;

&lt;p&gt;The testers kept at it, using the special diagnostic build, and eventually one of them struck gold by getting our game to crash &lt;em&gt;before&lt;/em&gt; it quit (rather than after sending bogus arguments to the boot loader), which caused our fatal error handler to run, which drew the stack trace you can see in the screenshot at the right. Knowing what I now know about the cause of the bug, the window of opportunity for this crash was &lt;em&gt;very&lt;/em&gt; small, and I dread to think about how many times they had to provoke the bug before they got this result. Testers, I salute your dedication!&lt;/p&gt;

&lt;p&gt;On the PowerPC, a DSI (Data Storage Interrupt) exception “occurs when a data memory access cannot be performed”—it’s the PowerPC equivalent of a segmentation fault or memory protection failure. Consulting the application map, it was possible to decode the stack trace as follows (everything from &lt;code&gt;main&lt;/code&gt; up to &lt;code&gt;menuTick&lt;/code&gt; is our code, and then &lt;code&gt;Reboot&lt;/code&gt; and above are library code):&lt;/p&gt;

&lt;table class=ruled&gt;
&lt;tr&gt;&lt;th&gt;PC&lt;th&gt;function
&lt;tr&gt;&lt;td&gt;&lt;code&gt;800E9D5C&lt;/code&gt;&lt;td&gt;&lt;code&gt;strlen&lt;/code&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;800C8AF0&lt;/code&gt;&lt;td&gt;&lt;code&gt;PrepareBootArgs&lt;/code&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;800C8DE8&lt;/code&gt;&lt;td&gt;&lt;code&gt;Reboot&lt;/code&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;80097B54&lt;/code&gt;&lt;td&gt;&lt;code&gt;menuTick&lt;/code&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;8009FD90&lt;/code&gt;&lt;td&gt;&lt;code&gt;mainTick&lt;/code&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;800A0518&lt;/code&gt;&lt;td&gt;&lt;code&gt;main&lt;/code&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;800041A4&lt;/code&gt;&lt;td&gt;&lt;code&gt;_init&lt;/code&gt;
&lt;/table&gt;

&lt;p&gt;We didn’t have the source code for the library functions, but we did have a debugger, so armed with the stack trace we spent a morning stepping through the sequence of events, reading the assembler, and reverse-engineering the library. It became clear that &lt;code&gt;Reboot&lt;/code&gt; carried out the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Allocate a 4 KiB buffer starting at &lt;code&gt;0x81280000&lt;/code&gt;.
&lt;li&gt;Use this buffer to assemble the command-line arguments for the boot loader.
&lt;li&gt;Copy these command-line arguments to the protected area of memory that will survive the reboot. (This is where &lt;code&gt;strlen&lt;/code&gt; gets called, and where the DSI exception sometimes occurred.)
&lt;li&gt;Reboot.
&lt;/ol&gt;

&lt;p class="box sidebar" style=width:400px&gt;&lt;img height=500 src=http://garethrees.org/2013/01/06/bug/race.svg width=400&gt; The race condition that caused the bug: step 2 is asynchronous with steps 1 and 3, so may be interleaved with their execution.&lt;/p&gt;

&lt;p&gt;The writers of &lt;code&gt;Reboot&lt;/code&gt; didn’t worry about whether the memory at &lt;code&gt;0x81280000&lt;/code&gt; would be in use by the game, because the function never returns. And if it never returns, it can’t possibly conflict with other uses of the memory. Or can it? We looked at our memory map, and in the RELEASE build &lt;code&gt;0x81280000&lt;/code&gt; was right in the middle of one of our external frame buffers.&lt;/p&gt;

&lt;p&gt;(For readers unfamiliar with video game programming: an &lt;em&gt;external frame buffer&lt;/em&gt; is a region of memory which contains a single video frame in a format suitable for sending to the display. Typically you have two external frame buffers: one is being displayed and the other is receiving (or waiting to receive) a copy of the next frame from the GPU. The GPU has direct access to main memory, so the copy happens asynchronously with respect to the CPU. When the next frame has been copied, the game waits for a safe moment (known as a ‘retrace’ or ‘vsync’ after the &lt;a href=http://en.wikipedia.org/wiki/Analog_television#Vertical_synchronization&gt;vertical retrace or synchronization&lt;/a&gt; on analog televisions) and then swaps the roles of the two external frame buffers, so that the second is now displayed and the first is now waiting for a copy of the next frame.)&lt;/p&gt;

&lt;p&gt;So even though we had programmed a wait for the graphics pipeline to empty before shutting down the game, there might still be a frame being worked on by the GPU, and if the timing were just right, then it would copy its last frame to the external frame buffer while &lt;code&gt;Reboot&lt;/code&gt; was assembling its command line in that same region of memory, corrupting it. (This would also explain why the crash only happened in the RELEASE build: in the DEBUG build the memory layout was different and the external framebuffers did not overlap the critical region.)&lt;/p&gt;

&lt;p&gt;It was easy to test this theory in the debugger: by putting a breakpoint in different places in &lt;code&gt;Reboot&lt;/code&gt; we could arrange for the copied framebuffer to cause both of the observed effects (the crash in &lt;code&gt;strlen&lt;/code&gt; and the corrupted arguments to the boot loader), and several other kinds of crash and corruption.&lt;/p&gt;

&lt;p&gt;So after all that, it was completely trivial to fix the bug. All we had to do was wait for the GPU to finish copying out the last frame (not just for the pipeline to clear) before calling &lt;code&gt;Reboot&lt;/code&gt;.&lt;/p&gt;
</description>
<pubDate>Sun, 06 Jan 2013 00:00:00 GMT</pubDate>
<guid>http://garethrees.org/2013/01/06/bug/</guid>
</item>
<item>
<title>The Hydrogen Sonata</title>
<link>http://garethrees.org/2013/01/02/banks/</link>
<description>&lt;img class=sidebar height=450 src=http://garethrees.org/2013/01/02/banks/cover.jpg width=300&gt;

&lt;p&gt;&lt;a href="http://www.amazon.co.uk/gp/product/0356501507/ref=as_li_ss_tl?ie=UTF8&amp;amp;tag=garethreesorg-21&amp;amp;linkCode=as2&amp;amp;camp=1634&amp;amp;creative=19450&amp;amp;creativeASIN=0356501507"&gt;&lt;cite&gt;The Hydrogen Sonata&lt;/cite&gt;&lt;/a&gt; is the ninth novel in the &lt;a href=http://en.wikipedia.org/wiki/Culture_series&gt;Culture series&lt;/a&gt; by Iain M. Banks. It is set in the civilization of the Gzilt, which is on the verge of ‘subliming’—that is, departing the physical universe to become immortal beings of pure energy. As the big day approaches, one question still niggles: what was the origin of the Gzilt’s suspiciously well-informed ‘Book of Truth’? Genuine religious inspiration or hoax by ancient meddling aliens? The only person who might know the answer is T. C. Vilabier,&lt;a href=http://garethrees.org/2013/01/02/banks/#note-1 id=noteref-1&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; composer of the famously unplayable sonata of the title,&lt;a href=http://garethrees.org/2013/01/02/banks/#note-2 id=noteref-2&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; and the only person who might be able to find him and persuade him to dish up the dirt is elevenstring player Vyr Cossont. And so a bunch of well-meaning observers from the Culture decide to set a quest in motion whose outcome may cause the Gzilt to think again about the whole sublimation project.&lt;/p&gt;

&lt;p&gt;‘Subliming’ started as a throw-away idea serving a narrative function in Banks’ 1987 novel &lt;a href="http://www.amazon.co.uk/gp/product/1857231384/ref=as_li_ss_tl?ie=UTF8&amp;amp;tag=garethreesorg-21&amp;amp;linkCode=as2&amp;amp;camp=1634&amp;amp;creative=19450&amp;amp;creativeASIN=1857231384"&gt;&lt;cite&gt;Consider Phlebas&lt;/cite&gt;&lt;/a&gt;. The idea that powerful species eventually mostly ‘retired’ from involvement in the material universe provided a quick explanation as to how relatively young civilisations like the Culture and the Idirans could be significant players in a galaxy that’s billions of years old:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Now it was obvious why the Dra’Azon had made Schar’s World one of their Planets of the Dead. If you were a pure-energy superspecies long retired from the normal, matter-based life of the galaxy, and your conceit was to cordon off and preserve the odd planet or two you thought might serve as a fitting monument to death and futility, Schar’s World with its short and sordid history sounded like the sort of place you’d put pretty near the top of your list.&lt;/blockquote&gt;

&lt;p&gt;The witty name ‘subliming’ for this phenomenon first appeared in &lt;a href="http://www.amazon.co.uk/gp/product/185723457X/ref=as_li_ss_tl?ie=UTF8&amp;amp;tag=garethreesorg-21&amp;amp;linkCode=as2&amp;amp;camp=1634&amp;amp;creative=19450&amp;amp;creativeASIN=185723457X"&gt;&lt;cite&gt;Excession&lt;/cite&gt;&lt;/a&gt; (1996), which added some details about the results of the process:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Sublimed Elders, become as gods to all intents and purposes, seemed to be derelict in the duties which the more naive and less developed societies they left behind ascribed to such entities. With certain very limited exceptions, the Elder species subsequently took almost nothing to do with the rest of life in the galaxy whose physical trappings they invariably left behind; tyrants went unchecked, hegemonies went unchallenged, genocides went unstopped and whole nascent civilisations were snuffed out just because their planet suffered a comet-strike or happened to be too near a super-nova, even though these events occurred under the metaphorical noses of the sublimed ones.&lt;/p&gt;

&lt;p&gt;The implication was that the very ideas, the actual concepts of good, of fairness and of justice just ceased to matter once one had gone for sublimation, no matter how creditable, progressive and unselfish one’s behaviour had been as a species pre-sublimation.&lt;/blockquote&gt;

&lt;p&gt;The trouble with taking this idea and running with it as the cornerstone of a whole novel is that “what we know” about subliming makes it look like a really unpromising theme: it’s a mysterious process (from &lt;a href="http://www.amazon.co.uk/gp/product/1841490598/ref=as_li_ss_tl?ie=UTF8&amp;amp;tag=garethreesorg-21&amp;amp;linkCode=as2&amp;amp;camp=1634&amp;amp;creative=19450&amp;amp;creativeASIN=1841490598"&gt;&lt;cite&gt;Look To Windward&lt;/cite&gt;&lt;/a&gt;: “the only way fully to understand it appeared to be to go ahead and do it”) typically preceded by “a degree of society-wide ennui”, and typically followed by quiescence.&lt;/p&gt;

&lt;p&gt;Banks joins this unpromising theme to a plot that makes no sense. The conflict in the novel arises because there is a faction among the Gzilt that are keen to ensure that their sublimation runs to schedule, and don’t want the distraction of a debate about the origin of the Book of Truth: so keen, in fact, that they are willing to commit mass murder and provoke interstellar war in order to cover up a message from the Zihdren admitting that it was all a childish prank and they are very sorry. But this driver of the plot is witless on multiple levels:&lt;/p&gt;

&lt;p&gt;First, in Banks’ Culture setting, it’s not clear what the difference is between a &lt;em&gt;genuine&lt;/em&gt; religious inspiration and one that’s merely handed down by godlike beings, nor why anyone would care. I’m open to the possibility that there might be genuine religious fervour, but I need to be convinced, and Banks doesn’t even try: the only character in the novel who actually &lt;em&gt;believes&lt;/em&gt; in the religion is Cossont’s mother, and she’s treated as a sad and deluded figure. (The villains who are out to suppress the relevation do so for reasons of &lt;em&gt;realpolitik&lt;/em&gt;: they are only concerned that &lt;em&gt;other&lt;/em&gt; people will care about it, and want to avoid a scandal at a politically inopportune moment.) Surely for the Gzilt, who are a populous advanced technological civilization in contact with many other civilizations perfectly capable of hoaxing up religious texts to order, the relevation cannot possibly come as a surprise? Every possible theory as to the origin of their religion will have been studied and debated by academics, and every crazy variation used as a plot device in popular media. The likelihood that it was a hoax is going to have been debated to death, and the society is going to have come to terms with it.&lt;a href=http://garethrees.org/2013/01/02/banks/#note-3 id=noteref-3&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Second, the revelation cannot possibly be compelling. The message that the Zihdren attempted to deliver, and the evidence uncovered by Cossont in the course of her quest, consist of digital records of eyewitness testimony from thousands of years ago. It’s well established in the Culture setting that this kind of evidence can easily be faked or mistaken or the result of brainwashing. So no-one who was really dogmatic about the Book of Truth would be put in a position where they were obliged to change their mind.&lt;/p&gt;

&lt;p&gt;Third, the motivation of the villains is premised crucially on the idea that information can be suppressed by secretly killing the messenger. How is this supposed to work in a world where everyone has access to networked information? Have these people not heard of backups? And in any case, the cover-up is bound to be worse than the crime. As explained above, it’s hard to imagine the relevation about the Book of Truth being much of a scandal in the first place. But the murder of the Zihdren messenger and then the mass murder of a group of people who happened to find out about it is something else entirely.&lt;/p&gt;

&lt;p&gt;Fourth, this kind of murderous cover-up seems very naïve: surely the modern public relations industry knows many better ways to suppress undesired revelations? Attack the credibility of the messenger; flood the network with distractions and misinformation; unleash an &lt;a href=http://www.newstatesman.com/politics/politics/2012/10/china%E2%80%99s-paid-trolls-meet-50-cent-party&gt;army of trolls&lt;/a&gt;. Or just leak the revelation early enough and let peoples’ short attention spans work their course.&lt;/p&gt;

&lt;p class=centred&gt;★&lt;/p&gt;

&lt;p&gt;Like several other of Banks’ Culture novels, &lt;cite&gt;The Hydrogen Sonata&lt;/cite&gt; is a bitter exercise in cynicism and disappointment. All expectations are dashed. The villains’ motivations are extraordinarily petty. No-one is punished for any of their crimes. The Culture ships directing Cossont’s quest decide (after far too many interminable committee meetings) not to tell anyone about what they have discovered. Cossont’s comically oversize elevenstring gets comically ferried round with her in spite of her ambivalence towards the instrument and in spite of the desperate situations from which it must be recovered, but then plays no role in events. Cossont has a robotic ‘familiar’ called Pyan, whose only role is to make occasional cute comments on the action. Cossont’s relationship with her mother is not resolved. Loose ends flap around everywhere you look.&lt;/p&gt;

&lt;p&gt;In fact, the novel is so permeated by a sense of ennui that you don’t have to read very deeply to see that it is a desperate plea from the author. Recast in metafictional terms it’s easy to decode the novel as follows: &lt;em&gt;The Culture series is preparing to depart to the great backlist in the sky, but there is a small possibility that an interesting plot development will be found that will keep it going. The author sets out on a quest in search of it, but the interesting development cannot be found on the planet Zyse; it cannot be found in high-tech laser battles between robots and mechas; it cannot be found in the evil motivations of evil villains; and it cannot be found in fights inside dirigibles, not even if the dirigibles fly around in a giant network of tubes. Gradually it becomes clear that the plot development is not so interesting after all. There’s simply no joy left in over-the-top evil and &lt;a href=http://garethrees.org/2011/03/25/surface-detail/#odv&gt;Obligatory Deadly Vengeance&lt;/a&gt; any more. The author decides it would be better not to follow up the plot development: better that the Culture series be left to quietly slip away into the twilight.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And I can’t help but agree.&lt;a href=http://garethrees.org/2013/01/02/banks/#note-4 id=noteref-4&gt;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;ol&gt;

&lt;li id=note-1&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/01/02/banks/#noteref-1&gt;^&lt;/a&gt; This isn’t, strictly speaking, a correct summary of the book. In fact, Vilabier is dead, and it’s his old friend QiRia who’s the object of the quest. But the shorter plot summary is much clearer, and it’s not as if you know or care who any of these people are anyway.&lt;/p&gt;

&lt;li id=note-2&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/01/02/banks/#noteref-2&gt;^&lt;/a&gt; This piece plays no other role in the events of the novel, and some reviewers apparently had difficulty with this (for example, &lt;a href=http://iansales.com/2012/11/27/from-the-sublime-to-the-ridiculous/&gt;Ian Sales&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The title has a thematic connection to the events in the novel rather than a literal connection. The sonata, we learn, was deliberately composed to be “near impossible [for humans] to play acceptably, let alone perfectly”, and even if it is played perfectly (for example, by a machine) then all that comes out is meaningless unpleasant noise: “As a challenge, without peer. As music, without merit.” So the question is, what value is there a human attempting to play it at all? But this obviously parallels the main question in the book, what value is there in living in the imperfect physical universe when you could sublime away into perfection? So the answer has to do with the joy of striving and the value of a goal that hasn’t yet been reached, as opposed to the sterility of perfection.&lt;/p&gt;

&lt;p&gt;I don’t know if Banks is making a deliberate reference, but the pieces that come immediately to mind are John Cage’s &lt;a href=http://en.wikipedia.org/wiki/Freeman_Etudes&gt;&lt;cite&gt;Freeman Études&lt;/cite&gt;&lt;/a&gt;, which were deliberately composed to be almost impossible to play by humans, and even if they are played very well (as in &lt;a href="http://www.youtube.com/watch?v=YflfGMo3O2Q"&gt;this performance by Irvine Arditti&lt;/a&gt;) all that comes out is meaningless noise. Cage describes the purpose of these études as a celebration of the ability to do hard work: “this music, which is almost impossible, gives an instance of the practicality of the impossible.” (See &lt;a href=http://www.rosewhitemusic.com/cage/texts/freeman.html&gt;James Pritchett&lt;/a&gt; for more analysis of these works.)&lt;/p&gt;

&lt;li id=note-3&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/01/02/banks/#noteref-3&gt;^&lt;/a&gt; &lt;a href="http://lareviewofbooks.org/article.php?type=&amp;amp;id=1014&amp;amp;fulltext=1"&gt;John Clute makes the same point&lt;/a&gt;: “Though we are in fact shown almost nothing of it, we are meant to think of the Gzilt civilization as enormously complex and sophisticated, most unlikely not to have taken on board centuries earlier the possibility that its Bible is a confidence trick; and Banks’s dodging of any attempt to create a portrait of Gzilt life only intensifies a sense that the McGuffin in this novel is an even more blatant damp squib than usual.”&lt;/p&gt;

&lt;li id=note-4&gt;&lt;p&gt;&lt;a href=http://garethrees.org/2013/01/02/banks/#noteref-4&gt;^&lt;/a&gt; I wrote this review before reading any others, and now that I’ve had a look around the web, it seems that I’m a bit of an outlier in my reaction. A few reviewers commented on how they felt let down by the ending, but treated it as an aberration in an otherwise enjoyable space adventure rather than a thematic part of a cynical whole. &lt;a href=http://www.fantasybookcafe.com/2012/11/review-of-the-hydrogen-sonata-by-iain-m-banks/&gt;Kristen at Fantasy Book Café&lt;/a&gt;: “Toward the end was very action-packed and exciting, but the conclusion seemed a bit hastily explained and was a bit of a letdown since not much was learned that hadn’t already been speculated about.” &lt;a href=http://radishreviews.com/2012/10/11/the-hydrogen-sonata-iain-m-banks/&gt;Donna at Radish Reviews&lt;/a&gt;: “It felt a little nihilistic, and that would be fine except it seemed to render the entire plot and galaxy tour Vyr goes on moot. Still, there are lots of clever, clever stops on that tour.” But the prize for best reading goes to &lt;a href=http://www.scotsman.com/lifestyle/books/features/book-review-the-hydrogen-sonata-by-iain-m-banks-1-2570941&gt;Andrew J. Wilson at the &lt;cite&gt;Scotsman&lt;/cite&gt;&lt;/a&gt;, who sees “a satire on the Scottish independence debate”.&lt;/p&gt;

&lt;/ol&gt;
</description>
<pubDate>Wed, 02 Jan 2013 00:00:00 GMT</pubDate>
<guid>http://garethrees.org/2013/01/02/banks/</guid>
</item>
</channel>
</rss>