<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>AlexNisnevich.blog</title>
 <link href="http://alex.nisnevich.com/atom.xml" rel="self"/>
 <link href="http://alex.nisnevich.com/"/>
 <updated>2026-02-17T08:10:16+00:00</updated>
 <id>http://alex.nisnevich.com/</id>
 <author>
   <name>Alex Nisnevich</name>
   <email>alex.nisnevich@gmail.com</email>
 </author>

 
 <entry>
   <title>Reading List - 2025</title>
   
    <link href="http://alex.nisnevich.com/blog/2026/02/08/reading_list_2025.html"/>
   
   <updated>2026-02-08T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2026/02/08/reading_list_2025</id>
   
    <content type="html">&lt;p&gt;Lots of stuff happening in my life right now, so my reading list fell by the wayside this year. Here it is, finally: all the books I read in 2025!&lt;/p&gt;

&lt;p&gt;Reviews shorter than usual this year.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; style=&quot;max-width: 300px&quot; src=&quot;/blog/images/readinglist2026.jpg&quot; /&gt;&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Art of Not Being Governed: An Anarchist History of Upland Southeast Asia&lt;/i&gt; – James C. Scott&lt;/div&gt;

&lt;p&gt;Scott’s anarchist reinterpretation of Southeast Asian history is densely written but very rewarding, greatly shaping my view of state formation, of zones of state control and lack of control, of Southeast Asia and its mind-boggling cultural diversity. &lt;em&gt;The Art of Not Being Governed&lt;/em&gt; is full of fascinating insights, too many to really list in a brief review. I particularly liked the chapters on oral tradition and heterodox religion, and how they could have historically been wielded as tools against state control.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Bewitched Bourgeois: Fifty Stories&lt;/i&gt; – Dino Buzzati [tr. Lawrence Venuti]&lt;/div&gt;

&lt;p&gt;A highly readable collection of quirky bite-size stories satirizing Italian society with a dash of magical realism, reading at times like a missing link between Kafka and Calvino. The Bewitched Bourgeois collects stories from throughout Buzzati’s long career, from his earliest works through to some posthumous pieces, but I found the mid-career stories from 1950 to 1960 to be particularly impressive, each one pretty much perfectly crafted.
Highlights: “Seven Floors”, “Panic at La Scala”, “The Prohibited Word”, “The Count’s Wife”.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Day Lasts More than a Hundred Years&lt;/i&gt; – Chingiz Aitmatov [tr. F. J. French]&lt;/div&gt;

&lt;p&gt;The Kazakh sections of the novel are a real masterpiece, effortlessly connecting the present and past, spiritual and political musings, and some captivating (invented) folklore tales, with a lyricism that comes through even in translation. Unfortunately, the science fiction narrative that is interspersed throughout is clunky and clumsily plotted, and I think the novel would have done better without it. Still, this is a novel I find myself thinking about even a year after reading it.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Sympathizer&lt;/i&gt; – Viet Thanh Nguyen&lt;/div&gt;

&lt;p&gt;Sometimes these very buzzy novels don’t live up to the hype (admittedly I’m a decade late in reading this one), but I liked &lt;em&gt;The Sympathizer&lt;/em&gt; overall, an exhilarating mix of spy thriller and literary reflection on the Vietnamese-American experience. Nguyen is at his best when he lets the novel be a novel: the writing is punchy and lyrical and for the most part the characters are complex and well-written, likeable despite their flaws. The novel really drags in the parts where the author seems more interested in addressing specific grievances than advancing the plot – in particular, I didn’t like the length filmmaking subplot in the middle, with its flat characters that seemed to function more as archetypes than real people.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Dream Hotel&lt;/i&gt; – Leila Lalami&lt;/div&gt;

&lt;p&gt;A very timely near-future novel exploring incarceration and surveillance, providing a contemporary spin on Kafka and Kesey. Lalami’s prose has a great flow to it, and I found myself unable to put &lt;em&gt;The Dream Hotel&lt;/em&gt; down, even as the plot progressively felt bleaker and bleaker.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Comic Book History of the Cocktail&lt;/i&gt; – David Wondrich and Dean Kotz&lt;/div&gt;

&lt;p&gt;Did the world need another David Wondrich cocktail history book? Apparently, yes! &lt;em&gt;The Comic Book History of the Cocktail&lt;/em&gt; is perhaps the perfect Wondrich book – it’s much broader in scope than &lt;em&gt;Imbibe!&lt;/em&gt; or &lt;em&gt;Punch&lt;/em&gt; but more accessible than the &lt;em&gt;Oxford Companion to Spirits and Cocktails&lt;/em&gt;, and the graphic-novel treatment actually fits the subject matter perfectly. It’s the perfect length: Wondrich and Kotz do a great job of covering the whole story of cocktails, from the origins of distillation through to the various contemporary cocktail subcultures, while still managing to fit all sorts of colorful details, and the book still never seems to drag. Plus, the recipes are legitimately good.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Istanbul: Memories and the City&lt;/i&gt; – Orhan Pamuk [tr. Maureen Freely]&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Istanbul&lt;/em&gt; is an unusual memoir: moody and dreamlike, dripping with nostalgia, jumping between topics in Turkish history, Istanbul geography and Pamuk’s own life, with shockingly personal recollections. The black-and-white photographs interspersed throughout, most from Pamuk’s own collection, do a fantastic job of setting the scene and often become objects of Pamuk’s musings in their own right, with some interesting art-analysis passages about them. I read this right after our trip to Istanbul but perhaps I would have gotten a little bit more out of it if I’d instead read it prior to the the trip.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Vineland&lt;/i&gt; – Thomas Pynchon&lt;/div&gt;

&lt;p&gt;I picked up &lt;em&gt;Vineland&lt;/em&gt; after hearing it inspired Anderson’s &lt;em&gt;One Battle After Another&lt;/em&gt;, only to be surprised to find that the novel and film really have very little in common – not plot or setting or characters, really just a general vibe. Still, Vineland immediate sucked me in with its darkly humorous look at early 1980s America (with more parallels to today’s world than I was expecting). It’s certain more accessible than, say, &lt;em&gt;Gravity’s Rainbow&lt;/em&gt;, but that’s not necessary a bad thing – on the contrary, I thought the faster pace of &lt;em&gt;Vineland&lt;/em&gt; helped Pynchon articulate his worldview in a clearer way. I thought it was almost a great book, but the abrupt ending was a letdown, with so many unresolved plot threads that it felt like Pynchon just got bored of writing.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Last Soviet Artist&lt;/i&gt; – Victoria Lomasko [tr. Bela Shayevich]&lt;/div&gt;

&lt;p&gt;&lt;em&gt;The Last Soviet Artist&lt;/em&gt;, Lomasko’s second collection to be translated into English, collects her graphic-novel ethnographic reporting from the Caucasus and Central Asia, as well as more personal pieces from the start of the Ukraine war to her being forced to leave Russia. As with her previous collection &lt;em&gt;Other Russias&lt;/em&gt;, I found the graphic-novel observational reporting style to be a powerful way to present the lives of marginalized peoples, but I wasn’t a fan of her more philosophical sections speculating about the role of artists in society.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Last Days of the Ottoman Empire&lt;/i&gt; – Ryan Gingeras&lt;/div&gt;

&lt;p&gt;Gingeras does a good job of making sense of a confusing period – Ottoman Turkey from the end of WWI through the founding of the Turkish Republic – dispelling nationalistic narratives and common misconceptions along the way. I appreciated that he didn’t shy away from discussing darker events of the period or from piercing the mythos around Atatürk.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Memories of the Future&lt;/i&gt; – Sigizmund Krzhizhanovsky [tr. Joanne Turnbull]&lt;/div&gt;

&lt;p&gt;A collection of speculative-fiction stories (and a novella) that were written in the early days of Soviet rule and never published for obvious reasons. At his best, Krzhizhanovsky has a Bulgakov-like ability to satirize the world around him through magical realism. But I found much of his prose to be murky and unclear – dreamlike, but not in a good way, without a clear sense of narrative progression. I’m not sure how much of this is stylistic and how much is due to the translation. I’ll try to re-read some of these stories in Russian at some point. Highlight: the novella “Memories of the Future”.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Happy-Go-Lucky&lt;/i&gt; – David Sedaris&lt;/div&gt;

&lt;p&gt;Sedaris hasn’t lost his sharp wit or his observational eye, but as he’s gotten older and more successful, his writing to me seems to have gotten less relatable and a little bit mean-spirited. I was put off by some stories that seemed an excuse to flaunt his wealth more than anything else, and his Balkan travel story put a bad taste in my mouth.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;32 Stories: The Complete Optic Nerve Mini-Comics&lt;/i&gt; – Adrian Tomine&lt;/div&gt;

&lt;p&gt;32 Stories collects Tomine’s original &lt;em&gt;Optic Nerve&lt;/em&gt; mini-comics, originally written between the ages of 16 and 20. It offers an interesting look at very early work from someone still developing his style, and it’s well-presented in a zine-like form factor that mimics the original self-published format of the mini-comics.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Getting Stoned with Savages: A Trip Through the Islands of Fiji and Vanuatu&lt;/i&gt; – J. Maarten Troost&lt;/div&gt;

&lt;p&gt;Unfortunate title aside, &lt;em&gt;Getting Stoned with Savages&lt;/em&gt; is an engaging and very funny travelogue detailing a year that Troost and his wife spent in Vanuatu, along with some time in Fiji. I appreciated at times the irreverent nature of Troost’s writing, but he does have a tendency to play up the more “exotic” elements of Vanuatu life in a way that doesn’t age super well.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The AI Con: How to Fight Big Tech's Hype and Create the Future We Want&lt;/i&gt; – Emily Bender and Alex Hanna&lt;/div&gt;

&lt;p&gt;Bender and Hanna do a reasonably good job at piercing the bubble of hype around the current LLM boom. I did feel like this book suffered from “preaching to the choir”, in the sense that I personally didn’t get too much out of it as someone already aligned with their position, and I  can’t imagine someone on the other side would pick this book up in the first place. I guess that’s unavoidable to some extent.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;To Kill a Bird with Two Stones: A Short History of Vanuatu&lt;/i&gt; – Jeremy MacClancy&lt;/div&gt;

&lt;p&gt;There aren’t many histories of Vanuatu, and I found MacClancy’s work to be reasonably comprehensive, if fairly dry. It was written in the early 80s, so it’s interesting to read about the events of 1980 from such a recent, if perhaps somewhat biased, perspective.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;1453: The Holy War for Constantinople&lt;/i&gt; – Roger Crowley&lt;/div&gt;

&lt;p&gt;Crowley explores the siege of Constantinople at varying time-scales, covering years of leadup to 1453 and then slowing down to an hour-by-hour recreation of the events of the siege itself. I thought that aspects of Crowley’s framing were a little old-fashioned, but I certainly learned a lot about a pivotal event that I’d only known the rough outline of before.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Murder on the Orient Express&lt;/i&gt; – Agatha Christie&lt;/div&gt;

&lt;p&gt;I know it’s a classic of the mystery genre and it certainly is a fun read, but I just found the ending unsatisfying, for reasons I can’t really express without spoiling the whole thing.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Deserters&lt;/i&gt; – Mathias Énard [tr. Charlotte Mandell]&lt;/div&gt;

&lt;p&gt;Enard intersperses two narratives here – one of a war deserter trying to survive somewhere in southern Europe, and one of a gathering of mathematicians that gets interrupted by the events of September 11th. Unfortunately I found both of these narratives to be a slog to read. I didn’t get much out of this novel.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Lively Isles: A Background to Vanuatu&lt;/i&gt; – David Luders&lt;/div&gt;

&lt;p&gt;A very “DIY” overview of Vanuatu’s history and culture, written by someone who’s spent a lot of time there but is hardly an expert. The book moves between topics haphazardly, interweaving history and myth in a confusing way that makes it hard to tell what is actually true.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;[unrated]&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Gathering the Pieces of Days&lt;/i&gt; – LeeAnn Pickrell&lt;/div&gt;

&lt;p&gt;I’ll leave this one unrated as it was written by a friend. &lt;em&gt;Gathering the Pieces of Days&lt;/em&gt; is a poetry collection with a unique premise: Pickrell kept a journal for a year, then adapted the events and musings of each week into a poem. An intriguing glimpse at the little moments that make up life.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Bislama Reference Grammar&lt;/i&gt; – Terry Crowley&lt;/div&gt;

&lt;p&gt;A readable and well-thought-out grammar of Bislama, the national creole language of Vanuatu. Leaving unrated as it’s unclear how to review a reference work like this.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Reading List - 2024</title>
   
    <link href="http://alex.nisnevich.com/blog/2025/02/02/reading_list_2024.html"/>
   
   <updated>2025-02-02T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2025/02/02/reading_list_2024</id>
   
    <content type="html">&lt;p&gt;It took me longer than usual to write my reviews this time (for a variety of reasons), but here it is: everything I read in 2024!&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★★★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Austerlitz&lt;/i&gt; – W. G. Sebald (tr. Anthea Bell)&lt;/div&gt;

&lt;p&gt;Sebald’s remarkable writing style sparkles even in translation. Although &lt;i&gt;Austerlitz&lt;/i&gt; is ultimately a novel about the Holocaust, Sebald approaches this heavy topic on an intimate scale, as we follow the titular Austerlitz’s journey to uncover his identity and his past over decades and across the European continent. &lt;i&gt;Austerlitz&lt;/i&gt; is gorgeously written, hard to put down (I ended up reading most of it in a single sitting), and ultimately devastating, with an emotional impact that crept up on me and lasted long after I put down the novel. Also remarkable are the photographs that Sebald intersperses the book with, all archival photographs that he reinterprets to be of and by the fictional Jacques Austerlitz.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Poor Things: Episodes from the Early Life of Archibald McCandless M.D., Scottish Public Health Officer&lt;/i&gt; – Alasdair Gray&lt;/div&gt;

&lt;p&gt;Reading &lt;i&gt;Poor Things&lt;/i&gt; after watching Lanthimos’s film adaptation, I was struck by just how much meatier the novel is – more defiantly Scottish, more proudly working-class, and also more intriguingly structurally, with a neat metafictional layer of framing narration and contradictory accounts that leave the reader bewildered as to what truly happened in the story. Gray seems to delight in these postmodern conceits, but what elevates this novel above so much other postmodern fiction that I’ve read lately is that all of Gray’s literary conceits are in service of a narrative that, for all of its layers of Gothic grotesqueness and Victorian pastiche and anti-capitalist critique, is rooted in something very fundamentally human: our desperate search for meaning in our lives and in the world.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Cahokia Jazz&lt;/i&gt; – Francis Spufford&lt;/div&gt;

&lt;p&gt;Spufford had already impressed me with &lt;i&gt;Red Plenty&lt;/i&gt;, but I was just floored by &lt;i&gt;Cahokia Jazz&lt;/i&gt;, which became the novel I recommended to the most people in 2024. &lt;i&gt;Cahokia Jazz&lt;/i&gt; takes place in an alternate-history 1920s America where the post-Columbian genocide never occurred, in an Indigenous-controlled state slowly being encroached on by American business interests. The detective plot seamlessly meshes with Spufford’s exploration of racial politics and the development of the main character, a mixed-race jazz-loving detective trying to find his place in the world. Spufford’s painstakingly detailed worldbuilding transports the reader to a unique, doomed world—a feeling I’d previously felt most closely in Mieville’s novels &lt;i&gt;The City &amp;amp; the City&lt;/i&gt; and &lt;i&gt;Embassytown&lt;/i&gt; and Chabon’s &lt;i&gt;The Yiddish Policemen’s Union&lt;/i&gt;. Spufford also demonstrates a clear love of early jazz, and the book is just so perfectly jazzy, in a way that doesn’t feel tacked on but is an integral part of the action. It’s great genre literature, yes, but &lt;i&gt;Cahokia Jazz&lt;/i&gt; also shows that great genre literature can be great literature in its own right.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Telegraph Avenue&lt;/i&gt; – Michael Chabon&lt;/div&gt;

&lt;p&gt;What starts as a simple tale of a record store going out of business blossoms into a kaleidoscopic epic that is also a love letter to a very specific micro-region of the Berkeley–Oakland border, circa 2004. &lt;i&gt;Telegraph Avenue&lt;/i&gt; is Chabon’s first work set in the Bay Area he lives in, and the attention to detail is so meticulous that I experienced an unexpected bout of nostalgia for a Bay Area past that I came here just a little bit too late to experience. The supporting characters are so vividly written, with such detailed backstories, that at least in one case, I was surprised to find that a specific character was a work of fiction and not an actual obscure jazz musician. And the music – you can practically hear the music from the pages as Chabon draws on what seems like an encyclopedic knowledge of jazz and funk. Like &lt;i&gt;Kavalier &amp;amp; Clay&lt;/i&gt;, the novel continues to escalate to practically fantastical levels until coming back down to reality in a poignant, bittersweet ending that masterfully ties up all loose ends.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Because Internet: Understanding the New Rules of Language&lt;/i&gt; – Gretchen McCulloch&lt;/div&gt;

&lt;p&gt;It’s refreshing to read a perspective on online language use that goes beyond tired clichés (the Internet is destroying our ability to communicate, etc.) and actually addresses what is so interesting about Internet English. McCulloch argues that the Internet provides something new to linguistics: a vast corpus of informal writing that falls closer to spoken language than written language, enabling us to better understand how we communicate with one another in practice. Throughout her examination of Internet English, McCulloch addresses sociolects, typographical tone, memes, and emojis, which she intriguingly argues occupy the same place online that gestures provide in face-to-face conversations.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Ladies of the Rachmaninoff Eyes&lt;/i&gt; – Henry van Dyck&lt;/div&gt;

&lt;p&gt;An absolute gem of a novella. Van Dyck has a masterfully light touch with the prose, and the dialogue is delightfully witty. Van Dyck’s style evokes the slightly old-fashioned feeling of a comedy of manners, but at the same time, &lt;i&gt;Ladies of the Rachmaninoff Eyes&lt;/i&gt; is surprisingly modern for 1965, daring to show the perspective of a closeted gay Black teen with levity and presenting a setting where racial harmony is so typical as to be unremarkable. It’s a delightful comic romp for the first three-quarters, with a powerful dollop of pathos at the end. Kudos to McNally Editions for republishing this out-of-print and forgotten masterpiece.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Croft and Ceilidh: or Corra-chagailte&lt;/i&gt; – Colin MacGilp MacDonald&lt;/div&gt;

&lt;p&gt;A collection of reminiscences about Scottish Highland life around the turn of the twentieth century, combining personal history, local folklore of the Ross-shire area, and reflections on the past and future of the Highlands. MacDonald wrote &lt;i&gt;Croft and Ceilidh&lt;/i&gt; in the 1940s, at a time when the culture he was writing about had already largely disappeared.
There’s certainly a tinge of nostalgia throughout the book, but there’s more to it than that. MacDonald is interested not just in describing scenes from Highland life but also in exploring what made this way of life possible and the factors that would eventually make it unsustainable.
Some sections of the book do seem to stretch the bounds of credulity, though not to the detriment of the whole work – MacDonald’s disarmingly casual, deliberately hokey style makes it hard to tell which of his anecdotes are from his own life, which ones come from stories others have told him, and which ones are perhaps wholly invented.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;SPQR: A History of Ancient Rome&lt;/i&gt; – Mary Beard&lt;/div&gt;

&lt;p&gt;Beard’s history of Rome combines academic rigor with a breezy writing style that’s a joy to read – not an easy feat. Throughout &lt;i&gt;SPQR&lt;/i&gt;, Beard investigates the foundational beliefs that Romans had about themselves, interrogates popular myths about Rome in light of recent discoveries, and illuminates ambiguities and current historical debates, showing that classical history is still very much a living and evolving field. 
Above all, she shows how the issues that ancient Romans dealt with are not so different from issues we grapple with today. In Beard’s words, “I no longer think […] that we have much to learn directly &lt;i&gt;from&lt;/i&gt; the Romans, […] but I am more and more convinced that we have an enormous amount to learn – as much about ourselves as about the past – by &lt;i&gt;engaging with&lt;/i&gt; the history of the Romans.”&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Malaysian Journey&lt;/i&gt; – Rehman Rashid&lt;/div&gt;

&lt;p&gt;&lt;i&gt;Malaysian Journey&lt;/i&gt; is an unusually structured memoir, with Rehman’s life story interleaved with vivid accounts of a journey he took through each state of Malaysia after a period of exile from the country in the 1980s. Rehman, a prominent journalist who often found himself at odds with the government, makes a point of demonstrating what Malaysia means to him – a multi-ethnic, multilingual society whose diversity is its key strength. The mix of history and memoir is a nice touch, with each providing context for the other, though I got lost in some of the sections on Malaysian politics, with so many acronyms and party factions. Ultimately, the book served as a compelling and poignant introduction to Malaysia, its history and culture, warts and all.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The End of Drum-Time&lt;/i&gt; – Hanna Pylväinen&lt;/div&gt;

&lt;p&gt;Pylväinen’s historical novel of cultural collision between Sámi herders and Nordic settlers in the 1850s is wonderfully written and deftly conjures up the atmosphere of life in the tundras of northern Scandinavia, with fleshed-out characters and evocative landscapes. The portrayal of nineteenth-century Sámi life rings true, and Pylväinen has done her research, spending extensive time staying with reindeer herders in Sápmi. There’s a lot I liked about this novel, and it’s a shame that the plot seemed thin and frankly dull at times, with long stretches of minimal exposition and a bewildering ending that I found not at all satisfying. &lt;i&gt;The End of Drum-Time&lt;/i&gt; explores a fascinating time and place, and I eagerly picked it up given my interest in far-Northern Indigenous cultures, but ultimately, the novel didn’t live up to my expectations.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Weird Black Girls: Stories&lt;/i&gt; – Elwin Cotman&lt;/div&gt;

&lt;p&gt;A collection of off-beat stories, mostly but not entirely in the genre of speculative fiction. Cotman offers a unique and much-needed voice to the genre and shines at melding the mundane with the fantastical in unexpected ways. The one story that departs from this and drops the fantasy entirely, “Triggered,” was also my least favorite – to me, it seemed mean-spirited and tonally odd compared to the rest. I particularly liked “The Switchin’ Tree”, “Tournament Arc”, and the novelette “Weird Black Girls.”&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;How Music Works&lt;/i&gt; – David Byrne&lt;/div&gt;

&lt;p&gt;If this is a rockstar memoir, it’s certainly an unconventional and thought-provoking one. Byrne digs deep into how music “happens,” interestingly devoting more space to topics like stage choreography and how musical scenes develop than the art of music itself. I particularly liked the “Creation in Reverse” chapter, which presents his theory of how musical genres develop to fit the physical spaces in which they’re performed, and his timely chapter (introduced in the newer 2017 edition) on “Infinite Choice: The Power of Curation.” However, the book got dragged down somewhat by the final two chapters, which I thought the book would have been better off without – in one, Byrne gets too mystical for my taste about the spiritual properties of music, and in the other, he bashes classical music in a way that feels tasteless to me.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Three Moments of an Explosion: Stories&lt;/i&gt; – China Mieville&lt;/div&gt;

&lt;p&gt;A wildly imaginative, wildly mixed collection of stories by Mieville. Did I like all of the stories in it? No. Did I understand what was happening in all the stories? Also no. But I was never bored, and at no point could I predict what would happen next in any of the stories – Mieville certainly shows himself to be a master of unexpected twists and turns. I particularly liked “The Dowager of Bees,” “In the Slopes,” “The Buzzard’s Egg,” and “Dreaded Outcome.”&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Ice Palace&lt;/i&gt; – Tarjei Vesaas (tr. Elizabeth Rokkan)&lt;/div&gt;

&lt;p&gt;Vesaas has a beautifully lyrical prose style that comes through even in translation from the Nynorsk, and his almost anthropomorphic depiction of the Norwegian landscape is masterful. But the pacing of this novella didn’t work for me – avoiding spoilers, the key action happens close to the beginning, and the rest of the novella follows other characters’ reactions to what happened. I can appreciate Vesaas’s attempt to explore grief, but I didn’t feel I got a good enough view of the main characters for this grief to be plausible.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Spiaking Singlish&lt;/i&gt; – Gwee Li Sui&lt;/div&gt;

&lt;p&gt;&lt;i&gt;Spiaking Singlish&lt;/i&gt; [sic] has a neat conceit – it’s an introduction to Colloquial Singaporean English written entirely in Singlish itself but presented in a way that is more-or-less legible to non-Singlish speakers, thus serving both as an introduction to the topolect and a forceful gesture against the Singaporean government’s “Speak Good English” campaign. I came out of it with a good understanding of the development of Singlish and a great deal of respect for the topolect, though sadly, I didn’t have much chance to practice it over my very short stay in Singapore last year.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Why Buildings Fall Down: How Structures Fail (2nd ed.)&lt;/i&gt; – Matthys Levy, Mario Salvadori&lt;/div&gt;

&lt;p&gt;A set of 18 case studies in specific building failures, with a cute story behind it (apparently, when Salvadori showed his mother-in-law his previous book &lt;i&gt;Why Buildings Stand Up&lt;/i&gt;, she replied that she’d be more interested in reading about the opposite). As someone who knows nothing about structural engineering, I found it interesting to read the case studies, but the book felt dumbed-down (aside from the appendices, there is no math at all) and disjointed. It felt like I was just presented with a series of disconnected and arbitrarily selected incidents and didn’t come out of it with any mental model of what exactly causes building failures to happen.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Red Team Blues&lt;/i&gt; – Cory Doctorow&lt;/div&gt;

&lt;p&gt;&lt;i&gt;Red Team Blues&lt;/i&gt; is an attempt at a detective novel that, unfortunately, has little in the way of the deduction that makes detective fiction interesting. The dialogue is frankly so stilted that it’s tough to read at times, the characters are one-dimensional, and the plot seems primarily structured to convey Doctorow’s points of view. This was my second attempt to read Doctorow in recent years, and while I agree with Doctorow on the issues he writes about, I can’t stand his didactic style, which seems even worse in his fiction than in his nonfiction. &lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Kreisleriana&lt;/i&gt; – E. T. A. Hoffman (tr. R. Murray Schafer and Max Knight, ed. David Charlton)&lt;/div&gt;

&lt;p&gt;Hoffman’s character of Johannes Kreisler, the bipolar conductor with such a mystical love of music that he’s unable to function in normal society, became a Romantic-era icon, inspiring compositions by Schumann, Brahms, and others. Unfortunately, I didn’t find &lt;i&gt;Kreisleriana&lt;/i&gt;, a collection of fictional journal entries, mostly music criticism, by the eponymous Kreisler, to be much of an engaging read. The various chapters straddle the bounds of fiction and essay, at times seeming to not differ so much from Hoffman’s own voice (after all, Hoffman himself was a notable music critic). Some chapters rise above the rest, particularly “Report of an Educated Young Man” – a comedic story following the adventures of an educated ape who loves music. But overall, reading &lt;i&gt;Kreisleriana&lt;/i&gt; felt like reading the musings of a fictional, very prickly music critic – an interesting concept, but certainly not for everyone. (Note to self: A better introduction to the character of Kreisler would probably have been Hoffman’s later novel &lt;i&gt;Kater Murr&lt;/i&gt;, which supposedly has more plot and features a cat).&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;[unrated]&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Mid-Holocene Language Connections between Asia and North America&lt;/i&gt; – Michael Fortescue, Edward Vajda&lt;/div&gt;

&lt;p&gt;I’ll leave this one unrated, as it doesn’t feel fair to “rate” academic writing. Both Fortescue and Vajda make strong arguments for their respective theses – the Uralo-Siberian hypothesis (of a link between Uralic and Eskimo-Aleut languages) and the Dene-Yeniseian hypothesis (of a link between Na-Dene languages and the small Yeniseian family in Siberia). Still, I didn’t end up entirely convinced by either. Actually, for the Dene-Yeniseian hypothesis, which I’ve loosely followed before, seeing Vajda’s cognate lists laid out made me realize the evidence for the hypothesis is more sketchy than I’d previously assumed. That doesn’t mean it’s necessarily false – it’s just very tough to conclusively demonstrate language relationships at such a distant time depth! &lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>A Belated Wordbots Dev Diary / Retrospective (~1 Year Post-Release)</title>
   
    <link href="http://alex.nisnevich.com/blog/2024/06/02/wordbots_retrospective.html"/>
   
   <updated>2024-06-02T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2024/06/02/wordbots_retrospective</id>
   
    <content type="html">&lt;p&gt;About a year ago, I “released”* &lt;a href=&quot;https://wordbots.io/&quot;&gt;Wordbots&lt;/a&gt;, the tactical card game with user-created cards that I’d been working on since 2016.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;(* I say “released” in quotes because this isn’t really the sort of game that will ever become feature-complete, especially not as a side project. But on &lt;a href=&quot;https://github.com/wordbots/wordbots-core/releases/tag/v0.20.0-beta&quot;&gt;April 29, 2023&lt;/a&gt;, I declared it fully playable and ready to graduate from alpha into a permanent “beta” stage, in which I’m no longer actively developing new features but continue to fix bugs and make minor improvements as time permits.&lt;/em&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Getting Wordbots from concept to fully playable beta was a &lt;em&gt;journey&lt;/em&gt;. It was one of the hardest things I’ve done in my life and by far the largest project I’ve ever started, in all senses of the word – the biggest in scope, the most time-intensive, and the most emotionally draining to work on. Would I have started working on Wordbots if I’d known how long it would take and what a torturous path I’d go on to “finish” it? I’m not sure. But in any case, I’m glad I did it.&lt;/p&gt;

&lt;p&gt;I set out to write a brief retrospective covering my experience developing Wordbots, from start to finish. I didn’t expect to write 6,000 words on the topic, so my apologies for the length of this post – I guess I had a lot to say!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(For those new to Wordbots, here’s &lt;a href=&quot;https://app.wordbots.io/about&quot;&gt;some information about what is&lt;/a&gt;, and here’s &lt;a href=&quot;https://app.wordbots.io/how-it-works&quot;&gt;more about its technical underpinnings&lt;/a&gt;!)&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;table-of-contents&quot;&gt;&lt;a name=&quot;toc&quot;&gt;&lt;/a&gt;Table of contents&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#visualization&quot;&gt;First, a visualization!&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#history&quot;&gt;A (not so) brief history of Wordbots&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#history-1&quot;&gt;The beginning (summer 2016–fall 2017)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#history-2&quot;&gt;Slow but steady improvement (fall 2017–summer 2019)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#history-3&quot;&gt;Distractions and demoralization (summer 2019–summer 2022)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#history-4&quot;&gt;The final push (fall 2022–spring 2023)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#history-5&quot;&gt;And finally, the release! (April 2023 and beyond)&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#what-went-right&quot;&gt;What went right&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#what-went-wrong&quot;&gt;What went wrong&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#final-thoughts&quot;&gt;Final thoughts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#acknowledgements&quot;&gt;Acknowledgements&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;first-a-visualization&quot;&gt;&lt;a name=&quot;visualization&quot;&gt;&lt;/a&gt;First, a visualization!&lt;/h2&gt;
&lt;p&gt;Before I talk about my successes and challenges with Wordbots, it might be helpful to throw up a little graph I made illustrating the development process as the story of git commits and releases, from my first-ever parser commit to just after the beta release:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/images/wordbots-dev-history.png&quot;&gt;&lt;img class=&quot;figure&quot; style=&quot;max-width: 100%&quot; src=&quot;/blog/images/wordbots-dev-history.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;center style=&quot;font-size: 0.8em; font-style: italic; margin-top: -8px;&quot;&gt;Wordbots commits and releases over time (click to embiggen)&lt;/center&gt;

&lt;p&gt;I should mention that I was fortunate to have a lot of help throughout this process, especially from &lt;a href=&quot;https://github.com/jacobnisnevich&quot;&gt;my brother&lt;/a&gt; but also from a whole host of people who made contributions of code or art or playtesting or feedback, &lt;a href=&quot;https://app.wordbots.io/about&quot;&gt;who are credited here&lt;/a&gt;. Still, for various reasons, I ended up making the vast majority of commits for both the core game and the parser, so this chart represents both the development cadence of Wordbots as a whole and my own personal journey working on it.&lt;/p&gt;

&lt;p&gt;As the chart makes painfully clear, working on Wordbots was not a totally smooth process. Let’s dig into how it all went down.&lt;/p&gt;

&lt;h2 id=&quot;a-not-so-brief-history-of-wordbots&quot;&gt;&lt;a name=&quot;history&quot;&gt;&lt;/a&gt;A (not so) brief history of Wordbots&lt;/h2&gt;

&lt;h3 id=&quot;the-beginning-summer-2016fall-2017&quot;&gt;&lt;a name=&quot;history-1&quot;&gt;&lt;/a&gt;The beginning (summer 2016–fall 2017)&lt;/h3&gt;
&lt;p&gt;The original idea for Wordbots came to me as I was working on open-sourcing &lt;a href=&quot;https://github.com/Workday/upshot-montague&quot;&gt;Montague&lt;/a&gt;, the semantic parsing library that I’d worked on with &lt;a href=&quot;https://github.com/turian&quot;&gt;Joseph Turian&lt;/a&gt; and Thomas Kim at the long-forgotten NLP startup &lt;a href=&quot;https://blog.workday.com/en-us/2015/workday-acquires-upshot-strengthens-data-science-expertise.html&quot;&gt;UPSHOT&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At its core, Montague is a fancy CKY parser that parses a &lt;a href=&quot;https://en.wikipedia.org/wiki/Combinatory_categorial_grammar&quot;&gt;CCG grammar&lt;/a&gt; and maps parsed tokens to semantic definitions, given as &lt;a href=&quot;https://en.wikipedia.org/wiki/Lambda_calculus&quot;&gt;lambda-calculus&lt;/a&gt; terms in a provided lexicon. It is &lt;em&gt;ancient&lt;/em&gt; technology by NLP standards (after all, the CKY parsing algorithm dates back to &lt;a href=&quot;https://aclanthology.org/1961.earlymt-1.31/&quot;&gt;a 1961 paper&lt;/a&gt;), but we came up with a clever design that ties these concepts together in a user-friendly way. In essence, Montague took something that had been &lt;em&gt;possible&lt;/em&gt; for decades (domain-constrained semantic parsing) and made it more accessible and (dare I say?) fun to implement.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; style=&quot;max-width: 70%&quot; src=&quot;/blog/images/montague-upshot-demo.png&quot; /&gt;&lt;/p&gt;
&lt;center style=&quot;font-size: 0.8em; font-style: italic; margin-top: -8px;&quot;&gt;Montague is a semantic parsing library, based on UPSHOT's English-&amp;gt;SQL parser&lt;/center&gt;

&lt;p&gt;Our original use case for semantic parsing at UPSHOT was translating English to database queries – hardly riveting stuff. But as we were writing the documentation for Montague, I began to brainstorm other possible applications for it, initially to figure out how to communicate the breadth of what our parser was capable of.&lt;/p&gt;

&lt;p&gt;After working through &lt;a href=&quot;https://github.com/Workday/upshot-montague?tab=readme-ov-file#getting-started&quot;&gt;a few toy examples&lt;/a&gt;, I started thinking of what semantic parsing could be used for within a gaming context. I thought back to card games like &lt;em&gt;Magic: the Gathering&lt;/em&gt; and &lt;em&gt;Fluxx&lt;/em&gt;, where individual cards (in &lt;em&gt;Fluxx&lt;/em&gt;, sometimes even player-made cards!) could completely alter the game’s rules. I’d also recently played and enjoyed the now-defunct online tactical card game &lt;a href=&quot;https://atomicbrawl.com/&quot;&gt;&lt;em&gt;Atomic Brawl&lt;/em&gt;&lt;/a&gt; and imagined a similar style of card game with cards that players could make themselves, with the card text automatically parsed and translated to a programming language, perhaps JavaScript. While writing the Montague README, I even mentioned this idea as an &lt;a href=&quot;https://github.com/Workday/upshot-montague?tab=readme-ov-file#applications&quot;&gt;“exercise for the reader”&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Applications Exercise 5. † Game semantics.&lt;/strong&gt; Come up with a semantic scheme for representing rule descriptions for a simple card game (think Magic, Hearthstone, etc., but simplify!) For example, a card may say something like “Whenever your opponent loses life, draw a card”. Then write a parser for it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We open-sourced Montague in March 2016, and Joseph and I &lt;a href=&quot;https://www.youtube.com/watch?v=lnV2JnNBM1I&quot;&gt;gave a talk about it&lt;/a&gt; at the Strata conference that June. That fall, I started playing with my game idea, building up a lexicon in Montague that could handle simple actions and trigger expressions like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;At the end of each turn, each creature takes 1 damage&quot;&lt;/code&gt;` and turn them into JavaScript code. But there was still no actual game that these cards supported – it was just a tech demo with an artificial lexicon vaguely evoking a positional card game.&lt;/p&gt;

&lt;p&gt;While visiting my family over Thanksgiving, I showed &lt;a href=&quot;https://github.com/jacobnisnevich&quot;&gt;my brother Jacob&lt;/a&gt; what I’d built, and he was clearly as excited about the concept as I was. He had some React experience at the time (I didn’t yet), and we quickly threw together a game prototype in React over the long weekend, using Hannu Kärkkäinen’s &lt;a href=&quot;https://github.com/hellenic/react-hexgrid&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;react-hex-grid&lt;/code&gt;&lt;/a&gt; library for the hex grid rendering and logic. By the end of the year, we had a barebones UI that could render a board and cards – nothing resembling a game yet, but enough of a skeleton that we could envision a path forward:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; style=&quot;max-width: 50%&quot; src=&quot;/blog/images/wordbots-f25aaa8630acada25c64eb61bbdaf0350224cbf8.png&quot; /&gt;&lt;/p&gt;
&lt;center style=&quot;font-size: 0.8em; font-style: italic; margin-top: -8px;&quot;&gt;A *very* early Wordbots commit&lt;/center&gt;

&lt;p&gt;I left my job at the start of 2017 and decided that I may as well take advantage of my newfound free time. I resolved to stay “funemployed” until the end of the year and try to finish Wordbots by then. I started working on Wordbots full-time in January, gaining proficiency in the React ecosystem along the way, and Jacob and I were able to make rapid progress on the prototype. By early April, we reached our v0.1.0 milestone: a fully functional prototype with working card creation and multiplayer gameplay, albeit limited features aside from that &lt;em&gt;(and it certainly wasn’t much to look at!)&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; style=&quot;max-width: 70%&quot; src=&quot;/blog/images/wordbots-v0.2.0-alpha.png&quot; /&gt;&lt;/p&gt;
&lt;center style=&quot;font-size: 0.8em; font-style: italic; margin-top: -8px;&quot;&gt;Wordbots v0.1.0 (April 2017)&lt;/center&gt;

&lt;p&gt;Progress continued quickly as I built up steam. By the end of April &lt;em&gt;(v0.4.0)&lt;/em&gt;, we had spectator support, activated abilities, card import/export, turn timer, and a huge amount of new card mechanics. By the end of May &lt;em&gt;(v0.5.4)&lt;/em&gt;: user accounts, auto-generated documentation, SFX, and more card mechanics. June &lt;em&gt;(v0.6.2)&lt;/em&gt;: tutorial mode and the “Did You Mean” feature in the card creator. July &lt;em&gt;(v0.7.0)&lt;/em&gt;: practice mode, in-game animations, and a major UX redesign.&lt;/p&gt;

&lt;p&gt;We were quickly running through our initial feature roadmap, and an end seemed in sight. But I couldn’t maintain this pace for long.&lt;/p&gt;

&lt;h3 id=&quot;slow-but-steady-improvement-fall-2017summer-2019&quot;&gt;&lt;a name=&quot;history-2&quot;&gt;&lt;/a&gt;Slow but steady improvement (fall 2017–summer 2019)&lt;/h3&gt;

&lt;p&gt;I’d planned to take all of 2017 off of work, but for various reasons (including a health insurance snafu), I ended up interviewing again in the summer. I started my current job in the fall, and this, combined with a couple of big international trips that I took in quick succession, got me out of the Wordbots groove.&lt;/p&gt;

&lt;p&gt;The next few months didn’t bring many major new gameplay features. With my more limited free time, I worked more on ironing out things at the margins: tweaking the UX, fixing bugs, adding new things to the parser, and generally trying to make things more user-friendly.&lt;/p&gt;

&lt;p&gt;In April 2018, Jacob and I felt the game was sufficiently polished for an official playtesting round. We’d shown it to a few people before, but never anything like this: we solicited participants in our social networks, ultimately sending out a link as well as some playtesting guidelines to 30 of our friends. And then we waited … And waited … And waited … Of our 30 playtesters, only two gave us substantial feedback, and the vast majority never responded at all. As far as I could tell, even people who really wanted to try Wordbots seemed to have been thoroughly stumped by the experience of trying to “play” it.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; style=&quot;max-width: 20%&quot; src=&quot;/blog/images/wordbots-feedback.png&quot; /&gt;&lt;/p&gt;
&lt;center style=&quot;font-size: 0.8em; font-style: italic; margin-top: -8px;&quot;&gt;Our sad playtest spreadsheet (April 2018)&lt;/center&gt;

&lt;p&gt;Needless to say, the failed playtest round was demoralizing for us. Still, it was helpful, if disheartening, to learn that our new-player user experience was, to be frank, garbage, and we began to rethink our priorities. One critical conversation that started from this was our attempt to answer a question that we’d been punting on up until this point:&lt;/p&gt;

&lt;center style=&quot;font-weight: bold; font-style: italic&quot;&gt;How could we make playing Wordbots a &quot;fair&quot; experience?&lt;/center&gt;

&lt;p&gt;After all, if players can design any cards they want, nothing is stopping a player from making some kind of “Win the game immediately” card and stuffing their deck with it. And this would be totally legal in the only Wordbots gameplay format that existed at the time &lt;em&gt;(we now call this the “Anything Goes” format and discourage people from playing it except with their friends)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We brainstormed a few possible solutions to the fairness question:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Use some kind of ML model to predict how powerful given cards are and assign them in-game energy costs accordingly?&lt;/li&gt;
  &lt;li&gt;Set up a server constantly simulating games between AI players with various cards to determine empirically how effective each card is in practice?&lt;/li&gt;
  &lt;li&gt;Set up some kind of market-based mechanic where players could trade cards for in-game currency, where a given card’s market value would determine if it was a “fair” card or not?&lt;/li&gt;
  &lt;li&gt;Allow players to make whatever cards they want, but establish game formats where each player is equally likely to have access to an overpowered card in-game?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final approach seemed to be the only feasible one. Based on player feedback &lt;em&gt;(thanks, Adam B!)&lt;/em&gt;, we developed a format we called Shared-Deck &lt;em&gt;(later renamed Mash-Up)&lt;/em&gt;, where each player brings their own deck to the game, but both players’ decks are shuffled into one mega-deck that both players draw from throughout the game. The Shared-Deck/Mash-Up format injected new life into Wordbots and made matches exciting again. For perhaps the first time, I actually found myself having fun &lt;em&gt;playing&lt;/em&gt; Wordbots with people, not just developing it.&lt;/p&gt;

&lt;p&gt;In the fall, thanks to some word-of-mouth among Jacob’s friends, we’d assembled a small team of people interested in working on Wordbots and even started holding regular standups. Unfortunately, it proved hard to divide the work, and Jacob and I still ended up doing the bulk of it. Still, having the regular cadence of standups helped maintain developer interest, at least amongst Jacob and me. And our broader team provided a useful sounding board for trying new ideas and for deciding how to prioritize our work.&lt;/p&gt;

&lt;p&gt;Around this time, a succession of particularly hard-to-catch bugs convinced us that it would be worth it to begin migrating the codebase from JavaScript to TypeScript. I was initially leery of such a massive undertaking but was convinced to try TypeScriptifying a little bit at a time, starting with the particularly brittle multiplayer code (and eventually ending, about two years later, with the React components). Little by little, we gained type safety throughout the Wordbots client code. Though it was a slog at times, the TypeScript refactor ultimately paid dividends by eliminating whole classes of bugs and making significant chunks of our code easier to reason about.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; style=&quot;max-width: 70%&quot; src=&quot;/blog/images/wordbots-loc.png&quot; /&gt;&lt;/p&gt;
&lt;center style=&quot;font-size: 0.8em; font-style: italic; margin-top: -8px;&quot;&gt;JavaScript vs TypeScript LOC in Wordbots, illustrating the pace of our big TypeScript refactor&lt;/center&gt;

&lt;p&gt;In April 2019, with v0.12.0 of Wordbots, we introduced the concept of “sets”, another answer to the “making Wordbots fair” question. Sets provided a way for players to curate their own collections of cards and challenge other players to make decks using only those cards. Finally, there was now a way for players to build and use thematic decks of player-made cards without worrying about huge power imbalances.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; style=&quot;max-width: 70%&quot; src=&quot;/blog/images/wordbots-sets-description.png&quot; /&gt;&lt;/p&gt;
&lt;center style=&quot;font-size: 0.8em; font-style: italic; margin-top: -8px;&quot;&gt;Wordbots's help text on the &lt;a href=&quot;https://app.wordbots.io/sets&quot;&gt;Sets&lt;/a&gt; page. Note that the &lt;b&gt;Set Draft&lt;/b&gt; format wasn't added until 2021.&lt;/center&gt;

&lt;p&gt;I intended Sets to be the last major concept added to Wordbots and began to think about what it would take to finally release the game to the wider world. I wrote a document called “Wordbots: The Final Stretch,” spelling out the remaining tasks needed to finish Wordbots, in the four categories of User-Friendliness, Robustness, Community, and Accessibility. Of course, “final stretch” turned out to be a little optimistic.&lt;/p&gt;

&lt;h3 id=&quot;distractions-and-demoralization-summer-2019summer-2022&quot;&gt;&lt;a name=&quot;history-3&quot;&gt;&lt;/a&gt;Distractions and demoralization (summer 2019–summer 2022)&lt;/h3&gt;
&lt;p&gt;By the summer of 2019, the amount of free time I had to spend on Wordbots plummeted once more as I began to spend every available weekend going to open houses, as part of our quixotic quest to buy a fourplex with our friends to live in – another massive “side project” with huge emotional highs and lows, that perhaps I’ll write about at a later point. In December 2019, we finally closed on a fourplex that ticked off all our boxes but required renovation on a massive scale. And, of course, we know what happened at the start of 2020. We were very fortunate to have the built-in community of our fourplex housemates to weather the lockdown with, but it was still an emotionally draining time. I had the mental bandwidth to manage precisely one project in 2020, and that ended up being the home renovation work, which took the better part of the year and was all-consuming while it was happening.&lt;/p&gt;

&lt;p&gt;When I circled my thoughts back to Wordbots in this period, I could no longer picture the end goal; it was just a mountain of work that looked more and more daunting as I spent more time away from it. I began to dread the thought of working on Wordbots, but at the same time, I felt guilty for not working on it. There were times when I couldn’t sleep because I was so angry with myself at “wasting my time”, for putting so much work into this project just to abandon it …&lt;/p&gt;

&lt;p&gt;Throughout it all, I did manage some intermittent bursts of motivation to work on Wordbots. I was able to finally finish that massive TypeScript refactor by the end of 2020 – it was a good thing to work on while my motivation was limited because I could think of it almost as a puzzle of getting all the types to line up. Around this time, I also slogged through a significant refactor of how cards are stored in the backend, developing a much more robust system of metadata tracking for cards that fixed a number of bugs and enabled many small quality-of-life features down the line.&lt;/p&gt;

&lt;p&gt;During a weeklong “Wordbots retreat” in the Lost Coast (a location strategically chosen for poor network connectivity and few distractions) in the summer of 2021, I powered through the work of creating the Set Draft format, a feature that we’d been discussing as a team for a while, and that quickly became my favorite way to play Wordbots. Drafting provided an elegant solution to the “fairness” issue (since either player was equally likely to draft a given card) while also removing the friction of players having to create a deck before playing. With the Set Draft format implemented in v0.16.0, I could finally see the path forward that had eluded me for the past few years.&lt;/p&gt;

&lt;p&gt;Other than that, the major gameplay improvements during this time were all aimed at providing a better UX for new players, including a massive interface redesign, art by &lt;a href=&quot;https://www.artstation.com/christopherwooten&quot;&gt;Chris Wooten&lt;/a&gt;, Help and Community pages, a “New Here?” feature, and more flavor throughout the game, as well as flavor text support for cards.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; style=&quot;max-width: 70%&quot; src=&quot;/blog/images/wordbots-v0.13.0-alpha.png&quot; /&gt;&lt;/p&gt;
&lt;center style=&quot;font-size: 0.8em; font-style: italic; margin-top: -8px;&quot;&gt;Wordbots home page before the interface redesign (v0.13.0, July 2019)&lt;/center&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; style=&quot;max-width: 70%&quot; src=&quot;/blog/images/wordbots-v0.18.0-alpha.png&quot; /&gt;&lt;/p&gt;
&lt;center style=&quot;font-size: 0.8em; font-style: italic; margin-top: -8px;&quot;&gt;Wordbots home page after the interface redesign (v0.18.0, October 2022)&lt;/center&gt;

&lt;h3 id=&quot;the-final-push-fall-2022spring-2023&quot;&gt;&lt;a name=&quot;history-4&quot;&gt;&lt;/a&gt;The final push (fall 2022–spring 2023)&lt;/h3&gt;

&lt;p&gt;I was finally able to get out of my slump and really mount a concerted effort to finish Wordbots in fall of 2022. I’d planned a two-month sabbatical from work to work on another project that fell through at the last minute, so I unexpectedly had a whole bunch of free time, so much free time that after a while I was finally able to push through my mental block and start working on Wordbots in earnest.&lt;/p&gt;

&lt;p&gt;In October 2022, I added one final major parser feature – and one that I could only have implemented while on sabbatical because of its colossal complexity – &lt;strong&gt;card rewrite effects&lt;/strong&gt;. Finally, Wordbots had an actual gameplay mechanic that no other card game could match – cards could rewrite the very text of other cards. It’s certainly a silly addition to Wordbots and perhaps not worth the ROI on the effort spent to make it possible. But it makes me smile, and so into the game it went.&lt;/p&gt;

&lt;p&gt;I also put a lot of work into a detailed &lt;a href=&quot;https://app.wordbots.io/how-it-works&quot;&gt;“How It Works” page&lt;/a&gt; describing the technical background behind Wordbots. I figured that, even if Wordbots was never going to be a successful multiplayer game with a thriving community (and at this point, I was resigned to this fate), at least it could be an interesting tech demo to a certain group of people like me.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://app.wordbots.io/static/help/how-it-works.png&quot;&gt;&lt;img class=&quot;figure&quot; style=&quot;max-width: 60%&quot; src=&quot;https://app.wordbots.io/static/help/how-it-works.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;center style=&quot;font-size: 0.8em; font-style: italic; margin-top: -8px;&quot;&gt;A visual summary of how Wordbots turns text into playable cards (click to embiggen)&lt;/center&gt;

&lt;h4 id=&quot;aside-wordbots-as-a-symbolic-ai-island-in-an-llm-world&quot;&gt;&lt;em&gt;Aside: Wordbots as a Symbolic AI island in an LLM world&lt;/em&gt;&lt;/h4&gt;

&lt;p&gt;Around this time, there was, of course, a much more significant development in the NLP world – the November 2022 release of ChatGPT, which changed the public conversation about NLP seemingly overnight. As I grappled with all my feelings about the rise of large language models, one thing I struggled to figure out was, &lt;em&gt;&lt;strong&gt;“what is the place of Wordbots in the new NLP world?”&lt;/strong&gt;&lt;/em&gt; After all, as cool as the Wordbots concept seemed to me, it was built on old – let’s say &lt;em&gt;ancient&lt;/em&gt; – technology.&lt;/p&gt;

&lt;p&gt;I ultimately settled on the idea of Wordbots as a demonstration of the continued relevance of older NLP approaches. As powerful as statistical NLP is today, there’s still value in symbolic AI methods. Could an LLM be used to produce code from card text the way Wordbots does? I’m sure such a thing could be implemented. Still, Wordbots’s symbolic AI underpinnings offer some valuable advantages, chiefly consistency and interpretability. Each term in Wordbots’s lexicon can be used repeatedly in consistent, predictable ways, just like natural language works, and not quite how LLMs tend to work with language. And the Wordbots parser is hardly a black box: every card in the game is happy to show off its full parse tree:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://app.wordbots.io/static/help/how-it-works.png&quot;&gt;&lt;img class=&quot;figure&quot; style=&quot;max-width: 100%&quot; src=&quot;https://app.wordbots.io/static/help/semantic-parse.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;center style=&quot;font-size: 0.8em; font-style: italic; margin-top: -8px;&quot;&gt;Zoomed-in parse tree from the diagram above (click to embiggen)&lt;/center&gt;

&lt;p&gt;This new way of thinking about Wordbots’ purpose helped inspire me during the final push towards beta release and also led me to write the slightly cheeky &lt;em&gt;“No LLMs were used in the making of Wordbots”&lt;/em&gt; disclaimer on the &lt;a href=&quot;https://wordbots.io/about&quot;&gt;About page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I finally began to work in earnest on checking off boxes on that old “Wordbots: The Final Stretch” doc I’d written back in 2019. Much had happened since then, but the overall roadmap was still solid, and I was able to pick up where I left off. Jacob and I started a weekly tradition of Wordbots playtests with each other, and over the next few months, we found and fixed dozens of issues with our multiplayer implementation. Other playtests helped us identify missing quality-of-life features that we proceeded to add, like support for draws, more robust game disconnection handling, support for card rarities in sets, etc. We load-tested the server to make sure it was up to the task of handling more players. We tried the game in every possible browser (including on tablets). By April 2023, we felt Wordbots had finally reached the point at which it could handle an influx of new players – it could survive in the wild.&lt;/p&gt;

&lt;h3 id=&quot;and-finally-the-release-april-2023-and-beyond&quot;&gt;&lt;a name=&quot;history-5&quot;&gt;&lt;/a&gt;And finally, the release! (April 2023 and beyond)&lt;/h3&gt;

&lt;p&gt;On April 29, 2023, we released Wordbots v0.20.0-beta. We were ready to show Wordbots to the world.&lt;/p&gt;

&lt;p&gt;To me, this really meant one thing: posting it on Hacker News. I took a deep breath and made a &lt;a href=&quot;https://news.ycombinator.com/item?id=35765274&quot;&gt;Show HN post&lt;/a&gt;, writing a little about my motivation and process behind Wordbots and providing a link. And then … crickets. The post got a few likes and quickly dropped off the front page.&lt;/p&gt;

&lt;p&gt;I was crushed. Seven years of work, and all that anguish, and all I had to show for it was a multiplayer game with no players, that nobody would ever see. And of course, I was mad at myself for having invested so much of my self-worth into a Hacker News post, and for relying so much on external validation rather than just appreciating Wordbots on its own merits. After all, wasn’t the journey the whole point?&lt;/p&gt;

&lt;p&gt;I tried posting about Wordbots on some subreddits and my social media, but nobody seemed interested. I resigned myself to the idea that maybe Wordbots just isn’t that cool an idea to people other than me and some of my friends …&lt;/p&gt;

&lt;p&gt;One night, a week or so after my initial HN post, on a whim (I might have been a little tipsy) I emailed &lt;a href=&quot;https://www.newyorker.com/news/letter-from-silicon-valley/the-lonely-work-of-moderating-hacker-news&quot;&gt;dang&lt;/a&gt;, the Hacker News moderator, asking him if I could please re-submit Wordbots to Hacker News with a more descriptive title that better explained what was so interesting about Wordbots. To my surprise, he responded and encouraged me to re-post it. &lt;a href=&quot;https://news.ycombinator.com/item?id=35879278&quot;&gt;I did&lt;/a&gt;, and this time around the reaction was much more positive, with a constructive discussion and a whole slew of new players joining the Wordbots community and immediately creating their own unique cards.&lt;/p&gt;

&lt;p&gt;Wordbots never became a huge phenomenon with a super active user base. And, to be fair, I didn’t expect it to (although it would have been nice!). Not having players constantly online is a bummer because when players do log in, they rarely have anyone to play with. We do have a &lt;a href=&quot;https://discord.gg/uBM4UbXSPB&quot;&gt;Discord server&lt;/a&gt; that is occasionally active, and some players have used it to schedule games and discuss cards they’re working on.&lt;/p&gt;

&lt;p&gt;In the year since Wordbots’s release, players have created 1441 unique cards and published &lt;a href=&quot;https://app.wordbots.io/sets&quot;&gt;14 sets&lt;/a&gt;. A few &lt;a href=&quot;https://app.wordbots.io/community&quot;&gt;die-hard Wordbots fans&lt;/a&gt; have gone on to make 100+ of their own cards. So that certainly feels like a small accomplishment!&lt;/p&gt;

&lt;p&gt;I’ll admit that I haven’t spent as much time working on Wordbots post-release as I’d have liked. I made one big bug-fix release a few months after beta but lost steam afterward and haven’t done much since. I’m definitely due for at least another round of bug fixes. But remarkably, the game has continued to function even without any intervention on my part – the server is still up, the parser still works, players are still creating cards, and occasionally games are being played, all without critical bugs. I’m pretty proud of what I’ve made, and I’m glad it’s still bringing joy to people.&lt;/p&gt;

&lt;h2 id=&quot;what-went-right&quot;&gt;&lt;a name=&quot;what-went-right&quot;&gt;&lt;/a&gt;What went right&lt;/h2&gt;

&lt;h4 id=&quot;1-sticking-through-to-completion&quot;&gt;1. Sticking through to completion&lt;/h4&gt;

&lt;p&gt;The single thing that I’m most glad about in the Wordbots development process is that, well … &lt;em&gt;we finished it!&lt;/em&gt; There were certainly various degrees of motivation throughout, and for a while there in 2020-2021, it seemed unlikely to me that I would ever be able to push this thing through to completion. Really, if there is one takeaway I want to offer from this massive, massive blog post, it’s “Stick with it!”&lt;/p&gt;

&lt;h4 id=&quot;2-investing-in-developer-productivity&quot;&gt;2. Investing in developer productivity&lt;/h4&gt;

&lt;p&gt;From the very beginning, investing in developer productivity was a major priority of mine. Every change to the parser’s lexicon and semantics was accompanied by unit tests verifying that given strings parsed to the right abstract representation, and it really wouldn’t have been possible to keep the parser stable without this, as even small lexical changes could have unexpected impacts on how different sentences parsed &lt;em&gt;(you can check out our current test suite &lt;a href=&quot;https://github.com/wordbots/wordbots-parser/blob/main/src/test/scala/wordbots/ParserSpec.scala&quot;&gt;here&lt;/a&gt;)&lt;/em&gt;. And early on in the development of the game client, I built a fairly comprehensive testing harness to make sure cards behaved as expected. &lt;em&gt;(Using Redux for state management actually made this fairly simple – we had one massive reducer that handled all in-game user interactions, and &lt;a href=&quot;https://github.com/wordbots/wordbots-core/blob/main/test/reducers/game.spec.ts&quot;&gt;most of our tests&lt;/a&gt; just consisted of simulating actions against this reducer and checking the resulting state.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As our work on Wordbots continued, we made a series of major refactors in 2018–2020 – migrating the entire JavaScript codebase to TypeScript, migrating from Material UI v0 to v1, and completely overhauling how cards were stored in our Firebase database. While these refactors took an immense amount of work in the moment, they greatly improved our confidence in the code and the stability of the whole system, and I’m not sure we would have been able to get Wordbots to beta without them.&lt;/p&gt;

&lt;h4 id=&quot;3-soliciting-feedback-throughout&quot;&gt;3. Soliciting feedback throughout&lt;/h4&gt;

&lt;p&gt;So much of what makes Wordbots work today – from the game formats that make games fair to all sorts of card behaviors all the way to the look and feel of the interface itself – owe their existence to player feedback. I’m really glad that we continued to playtest Wordbots and continually showed it to new people throughout the development process (even if some of the playtest rounds were demoralizing to Jacob and me due to lack of player interest).&lt;/p&gt;

&lt;p&gt;I also just want to mention my immense gratitude to our mega-testers and sounding boards, who gave us the feedback and critique that led to so many features and fixes: Asali, James, Annie, Danny, Adam, Liam, Honza – thank you!&lt;/p&gt;

&lt;h4 id=&quot;4-me-working-throughout-the-whole-stack-from-the-game-client-to-the-parser&quot;&gt;4. Me working throughout the whole stack, from the game client to the parser&lt;/h4&gt;

&lt;p&gt;This can also be thought of as a con &lt;em&gt;(after all, it’s really a symptom of me not being able to build up a team like I wanted to)&lt;/em&gt;, but on the bright side, the fact that I had to work on every single part of Wordbots was liberating in the sense that as I lost steam working on one part of the game, I always had the option to shift gears to other parts. This proved very helpful, especially during the lower-motivation periods – oftentimes, my gateway drug for working on Wordbots was fixing parser bugs or adding new vocabulary to the parser, as these were usually such self-contained projects that it was easy to pick up and work on them for short periods of time.&lt;/p&gt;

&lt;h4 id=&quot;5-the-wordbots-concept-itself&quot;&gt;5. The Wordbots concept itself&lt;/h4&gt;

&lt;p&gt;Finally, I should mention the general Wordbots concept itself as a major “pro” – after all, it was a strong enough idea that the vision was able to push us through to the end despite all the challenges along the way! I can’t think of many projects I’d be able to work on through to completion for seven years, but this has definitely been one of them.&lt;/p&gt;

&lt;h2 id=&quot;what-went-wrong&quot;&gt;&lt;a name=&quot;what-went-wrong&quot;&gt;&lt;/a&gt;What went wrong&lt;/h2&gt;

&lt;h4 id=&quot;1-struggling-with-motivation&quot;&gt;1. Struggling with motivation&lt;/h4&gt;

&lt;p&gt;In an ideal world, I would have maintained the same level of motivation that I had at the start of the project all the way through to completion. Of course, this isn’t what ended up happening. For a variety of reasons – lack of time, self-doubt, not being sure about what Wordbot’s place in the world was – I struggled with motivation at various times and had a particularly low period in 2020–2022 where I thought of Wordbots as an obligation rather than a fun project to work on.&lt;/p&gt;

&lt;p&gt;Could I have done it differently? I could imagine a scenario where I hadn’t run out of steam by the end of 2019 and finished the Wordbots beta then. In that case, we probably wouldn’t have gotten key features like drafting formats and card rewrite mechanics – I hadn’t thought of those yet! But the core of the game would have been released to the world years earlier, with so much less mental anguish on my part. But it’s futile to think about those kinds of hypotheticals.&lt;/p&gt;

&lt;h4 id=&quot;2-making-a-multiplayer-game-in-the-first-place&quot;&gt;2. Making a multiplayer game in the first place&lt;/h4&gt;

&lt;p&gt;While Wordbots’s multiplayer gameplay is undoubtedly a core part of the experience, I do sometimes wish I’d found some way to implement my original “semantic parsing for game mechanics” idea &lt;em&gt;without&lt;/em&gt; the multiplayer part. The issue is that multiplayer games require a community of players, and there’s a chicken-and-egg problem in that players aren’t going to keep logging in if there’s nobody to play with, so jumpstarting a community is difficult. While Wordbots &lt;em&gt;does&lt;/em&gt; get some activity from time to time, the average visitor struggles to find anyone to play with. I don’t feel as satisfied about Wordbots’s release as I do about my last major game, &lt;a href=&quot;https://alexnisnevich.github.io/untrusted/&quot;&gt;&lt;em&gt;Untrusted&lt;/em&gt;&lt;/a&gt;, which, as a self-contained single-player puzzle game, is always playable for anybody who wants to try it.&lt;/p&gt;

&lt;p&gt;(The issues with building a multiplayer game also tied into my struggles with motivation, as I slowly realized that there would never be a stable Wordbots player base, causing me to question from time to time what the purpose of Wordbots even was.)&lt;/p&gt;

&lt;p&gt;What could a single-player Wordbots have looked like? I’m not entirely sure. At one point I imagined the concept of a Wordbots “puzzle mode” (inspired perhaps by the &lt;a href=&quot;https://faeria.fandom.com/wiki/Solo_mode&quot;&gt;puzzle mode in &lt;em&gt;Faeria&lt;/em&gt;&lt;/a&gt;), where the player is required to win the game in one move from predetermined board positions, with the added conceit that certain cards in the player’s hand would have missing words or phrases, and the player could drag and drop from a pool of phrases to fill in the cards in a way that would make the puzzle winnable. I imagined it as kind of a &lt;em&gt;Faeria&lt;/em&gt; (tactical card game puzzle) meets &lt;em&gt;Untrusted&lt;/em&gt; (fill in the right words to make a level winnable) meets &lt;em&gt;The Incredible Machine&lt;/em&gt; (drag and drop tools to complete a challenge) sort of thing. I never attempted to implement this gameplay mode, but it certainly would have been an interesting route to try.&lt;/p&gt;

&lt;h4 id=&quot;3-not-thinking-about-the-new-player-experience-soon-enough&quot;&gt;3. Not thinking about the new player experience soon enough&lt;/h4&gt;

&lt;p&gt;It took us a long time to advance past the proof-of-concept stage, where Jacob and I sought to demonstrate that what we were building was possible from a technical standpoint. Throughout this time, we tended to prioritize new features over UX. And so, for most of our development process, Wordbots just didn’t feel inviting to new players. Even when we set out to run our biggest playtest round in 2018, we’d neglected to think about how bad the user experience was for new players; in the end, most aspiring playtesters didn’t even know what to do when seeing the Wordbots homepage for the first time.&lt;/p&gt;

&lt;p&gt;Fortunately, we turned this around and prioritized working on the UX for new players starting around 2020, aided by some helpful alpha testers. But we missed out on a lot of good potential feedback due to Wordbots’s interface being so confusing for the first half of the development process.&lt;/p&gt;

&lt;h4 id=&quot;4-never-finding-a-good-way-to-split-up-work-among-a-team&quot;&gt;4. Never finding a good way to split up work among a team&lt;/h4&gt;

&lt;p&gt;By the fall of 2018, Jacob and I had assembled a small group of developers who were passionate about the Wordbots concept and interested in helping us. Unfortunately, we never found a way to involve these eager developers in our development process. We’d have regular standups with the whole group, and Jacob and I would try to assign tasks to people, but for the most part, it still ended up being the two of us working on all development tasks (though the rest of the group still offered help in the form of playtesting and discussion of features).&lt;/p&gt;

&lt;p&gt;I think part of the problem was Wordbots’ inherent complexity: working on the parser requires pretty specialized knowledge, while the game client is relatively thorny itself due to having to deal with arbitrary card execution as well as all of the already complex systems required to run a multiplayer game. Even tasks that Jacob and I envisioned as simple and self-contained ended up being more complicated than intended, and without easy wins, it was hard for our potential collaborators to have the confidence to approach the rest of the system.&lt;/p&gt;

&lt;p&gt;And part of the problem was perhaps that Jacob and I aren’t great at process – we’re both better developers than we are project managers. Setting up a &lt;a href=&quot;https://zube.io/&quot;&gt;Zube&lt;/a&gt; kanban board linked to our GitHub and having occasional freeform standup meetings was about the best we could do, but it wasn’t really enough process to bring new people into the fold.&lt;/p&gt;

&lt;h4 id=&quot;5-spending-too-much-time-pursuing-dead-ends&quot;&gt;5. Spending too much time pursuing dead ends&lt;/h4&gt;

&lt;p&gt;Because Wordbots, at its core, started as a technical demo, it was easy to fall into what I could call a “hackathon mentality” while working on it. I kept falling into the trap of trying to build certain features just because they were technically interesting to work on, even if they were rarely important for the game itself.&lt;/p&gt;

&lt;p&gt;Some examples of times that either I or other Wordbots developers got lost in the weeds:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Automated cost calculation&lt;/strong&gt; – A few of us briefly went down the rabbit hole of trying to have the parsing server predict how much a given card ought to cost by going through its AST, but we quickly realized that this was not remotely feasible.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Random card generation for tests&lt;/strong&gt; – At one point, I got intrigued by the possibility of generating completely random legal cards for various stress-testing scenarios (for both the parser and the game client). Eventually, I managed some hacky Scala metaprogramming to generate random valid card ASTs within the parser. But for some reason, I got fixated on the idea of generating random well-formed card text, by somehow inverting the parser to turn it into a generator. After spending quite some time perusing the NLP literature in search of prior art on this, I finally gave up.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Server-side card rendering&lt;/strong&gt; – We have &lt;a href=&quot;https://github.com/wordbots/wordbots-core/blob/main/src/server/discordBot.ts&quot;&gt;a Discord bot&lt;/a&gt; that listens for exported Wordbot card JSON and renders the corresponding card in a readable way. Right now, it just prints a pretty text representation of the card, but for too long, I experimented with various ways to render the card as a PNG on the Wordbots server. The problem is that any technique for rendering React-to-image (and I tried quite a few techniques here) is inherently flaky, and I ran into constant struggles keeping the card-generation service working. Finally, I decided that just rendering cards as text would be sufficient for the Discord bot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And these are just the rabbit holes that I crawled back out of! Some rabbit holes I managed to get to the end of – card rewrite effects are a good example, or, say, the Scala macro hell that I went through to be able to pretty-print lambda expressions for good-looking parse trees in Montague. So, while I certainly wasted a lot of time on these features, at least for these, I had something to show at the end of it.&lt;/p&gt;

&lt;h4 id=&quot;6-sticking-to-tools-we-knew&quot;&gt;6. Sticking to tools we knew&lt;/h4&gt;

&lt;p&gt;We were able to get off to a running start by leveraging tools we were already familiar with – Scala 2.10 and &lt;a href=&quot;https://github.com/Workday/upshot-montague&quot;&gt;Montague&lt;/a&gt; on the parser side and the JavaScript+React+Redux+Material UI ecosystem on the client side – but this also proved to be a double-edged sword in some cases:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Using JavaScript from the beginning instead of TypeScript led to a painful migration process as we realized how crucial the type-safety guarantees of TypeScript were for what we were doing.&lt;/li&gt;
  &lt;li&gt;Material UI proved not to be an ideal UI framework for making things look “game-y”, and I had to mess with it intensively over many years to get Wordbots to look more like a game than like, say, a business application.&lt;/li&gt;
  &lt;li&gt;And my deep reliance on the Scala 2.10 macro system for the parse-tree-visualization feature of Montague makes it impossible to run the Wordbots parser with any version of Scala newer than 2014. Among other issues, this means that the Wordbots parser can never compile down to JavaScript (alas, &lt;a href=&quot;https://www.scala-js.org/&quot;&gt;Scala.js&lt;/a&gt; requires Scala 2.11+) for running in the browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;&lt;a name=&quot;final-thoughts&quot;&gt;&lt;/a&gt;Final thoughts&lt;/h2&gt;

&lt;p&gt;I suppose I should write some kind of conclusion, but I don’t know what else I have to add at this point. Working on Wordbots was an enormous undertaking, and while the journey was rocky, I’m glad to have made it to the other side. I learned a lot from this process, and it’s definitely made me a better developer. (And maybe a better project manager, too?)&lt;/p&gt;

&lt;p&gt;I don’t know what my next big project will be. I’m still somewhat recovering from the Wordbots process. I’d like to do something a little smaller in scope next, maybe something without an NLP component. But we’ll see!&lt;/p&gt;

&lt;h2 id=&quot;acknowledgements&quot;&gt;&lt;a name=&quot;acknowledgements&quot;&gt;&lt;/a&gt;Acknowledgements&lt;/h2&gt;

&lt;p&gt;Whew, that was a mouthful of a post! I want to end by once again thanking everyone who helped me make Wordbots a reality, from &lt;a href=&quot;https://wordbots.io/about&quot;&gt;everyone who contributed code, art, or other additions to the game &lt;/a&gt; to all the people around the world who’ve tried making their own cards or playing a round of Wordbots. Thank you all!&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; style=&quot;max-width: 70%&quot; src=&quot;https://app.wordbots.io/static/artAssets/dome2.png&quot; /&gt;&lt;/p&gt;
&lt;center style=&quot;font-size: 0.8em; font-style: italic; margin-top: -8px;&quot;&gt;Art by &lt;a href=&quot;https://www.artstation.com/christopherwooten&quot;&gt;Chris Wooten&lt;/a&gt;&lt;/center&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Reading List - 2023</title>
   
    <link href="http://alex.nisnevich.com/blog/2024/01/18/reading_list_2023.html"/>
   
   <updated>2024-01-18T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2024/01/18/reading_list_2023</id>
   
    <content type="html">&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Remains of the Day&lt;/i&gt; – Kazuo Ishiguro&lt;/div&gt;

&lt;p&gt;An aging butler taking a road trip in 1950s England seems like a thin premise for a novel, and yet Ishiguro makes it into a profoundly moving work of literature. The bulk of the story is told through the narrator’s recollections as he drives through the English countryside. He begins the journey fully committed to a particular ideal of being a “dignified” butler, which he expounds on in great detail. But by the end of it, cracks have appeared in his belief system – we see just how much damage his unwavering commitment to his ideals has caused to his personal life, just as we learn more about the shortcomings of the lord that all of this butlering was done for. At its heart, this is a novel about the ideas we build our lives around, and about what happens if these towers of ideas that we’ve built up no longer support us.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Understanding Comics: The Invisible Art&lt;/i&gt; – Scott McCloud&lt;/div&gt;

&lt;p&gt;I’ll admit that I haven’t explored the medium of graphic novels a huge amount in my life, but maybe this will change now that I’ve read &lt;em&gt;Understanding Comics.&lt;/em&gt; It illustrates the artistic and philosophical principles behind sequential art (iconography, time and motion, the idea of closure, etc), all in the form of a comic. It then goes beyond the mechanics of comics into deeper ideas about the creative process itself. I think everyone involved in any sort of artistic field, irrespective of one’s interest in comics, would benefit from reading &lt;em&gt;Understanding Comics&lt;/em&gt;.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Exercises in Style&lt;/i&gt; – Raymond Queneau (tr Barbara Wright)&lt;/div&gt;

&lt;p&gt;Queneau’s “exercises” themselves – the same short story retold in a hundred different styles, ranging from Haiku to “Mathematical” – and Wright’s classic translation are both an absolute tour-de-force of language. I don’t usually go for this kind of modernist literature, but I couldn’t help smiling at the cleverness of it all. I read a modern edition that additionally compiles Queneau’s previously unpublished exercises and includes new exercises from contemporary writers in such styles as “Beat” and “Cyberpunk.” For the most part, I could take or leave this added material.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Red Plenty&lt;/i&gt; – Francis Spufford&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Red Plenty&lt;/em&gt; is a gripping historical novel about the management of the Soviet economy in the 1960s. No, I’m not kidding. Except it’s not &lt;em&gt;exactly&lt;/em&gt; a novel – there’s a mix of fictional characters and real historical figures, with the latter’s dialogue largely lifted directly from primary sources. The narrative chapters are further interspersed with meticulously researched nonfiction chapters on Soviet economics. It’s not an easy topic to make into an engaging narrative, but Spufford achieves the impossible here. He was also clearly passionate about getting all the details right, going so far as to commission his own English translations of primary sources when he realized his lack of Russian knowledge was a barrier. (Even Spufford’s bibliographical footnotes are so inviting and enthusiastic that now I have a dozen more books I want to read about – who’d have guessed – science and economics in the Soviet Union.) Without a doubt, this is the best work of fiction I’ve read about the Soviet Union by a Western writer (sorry, Amor Towles).&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Ladies of Grace Adieu and Other Stories&lt;/i&gt; – Susanna Clarke&lt;/div&gt;

&lt;p&gt;&lt;em&gt;The Ladies of Grace Adieu&lt;/em&gt; is a collection of stories written in the style of 19th-century fairy tales, set in the world of Clarke’s &lt;em&gt;Jonathan Strange &amp;amp; Mr. Norrell&lt;/em&gt;. The stories are beautifully crafted in Clarke’s signature style, with a deceptively light tone hiding heavy themes. All in all, it was a delightful read, and I just wish it wasn’t so short. (I also definitely found myself missing the labyrinthine footnotes of &lt;em&gt;Jonathan Strange &amp;amp; Mr. Norrell&lt;/em&gt;.)&lt;/p&gt;

&lt;p&gt;I particularly liked “Mrs Mabb,” “Mr Simonelli,” and “Tom Brightwind.”&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Venice Observed&lt;/i&gt; – Mary McCarthy&lt;/div&gt;

&lt;p&gt;An absolute masterpiece of travel writing by one of the great essayists of the 20th century. &lt;em&gt;Venice Observed&lt;/em&gt;, written over the course of a year that McCarthy spent in Venice in the 1950s, humanizes and contextualizes a city that can too often seem like a cliché. It certainly changed my mind about Venice – after all, it may be a “tourist trap,” but it’s an honest one, having essentially invented the concept of the “tourist trap” in the 17th century. The first half of the book, focusing more on the city’s history, was just one fascinating insight after another. A highlight for me was the essay on how each of Venice’s principal islands essentially functions as a microcosm of Venice in a different time period. The book’s second half veered more into art history, which was still interesting but didn’t grip me as much.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Zeno's Conscience&lt;/i&gt; – Italo Svevo (tr Peter Palmieri)&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Zeno’s Conscience&lt;/em&gt; is a classic of modernist literature that seems to have lost some of its popularity – I’ll admit that I only picked it up because I was in Trieste, where Italo Svevo is something of a local hero. (And I’m glad I did!) The novel is more of a collection of stories, all told through the same framing device of a patient’s diary written for his psychotherapist (a pretty novel concept for 1927!). The patient in question, the titular Zeno, is a mess, but a highly self-aware mess, and he is constantly rationalizing his various compulsions and anxieties, such as his almost daily attempts to quit smoking just because “I believe the taste of a cigarette is more intense when it’s your last.” Zeno, as a character who is both profoundly flawed and tremendously observant, provides a perfect foil through which Svevo can satirize early-20th century Triestine society, as we see Zeno’s misadventures in the worlds of romance and business. In the end, “&lt;em&gt;La vita non è né brutta né bella, ma è originale!”&lt;/em&gt; (“Life is neither ugly nor beautiful, but it’s original!”)&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Edge of the World: A Cultural History of the North Sea and the Transformation of Europe&lt;/i&gt; – Michael Pye&lt;/div&gt;

&lt;p&gt;A clever and enjoyable work of pop history about the cultural influence of North Sea civilizations (in particular, Vikings, Frisians, and the Hansa) on Europe from the 7th through the 17th centuries, countering the traditional European narrative of cultural innovations largely stemming from around the Mediterranean. Pye has done his research, and the primary sources he cites provide remarkable glimpses of medieval lives not too different from our own, neatly organized into chapters covering topics ranging from the book trade to “love and capital” to Medieval fashion (an unexpectedly delightful chapter). My one major complaint is that the book often tries to provide simple causal explanations for complex phenomena (I call this the “Jared Diamond style” of pop history). I get why Poe does this – it certainly makes for a “neater” narrative – but it feels insincere and takes away from an otherwise excellent read.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;A Very Old Man: Stories&lt;/i&gt; – Italo Svevo (tr Frederika Randall)&lt;/div&gt;

&lt;p&gt;A collection of Svevo’s last writings, mostly prose fragments that continue the story of &lt;em&gt;Zeno’s Conscience&lt;/em&gt;. I’m grateful that these writings have been published and that readers have one last opportunity to enter Svevo’s Trieste. It’s still remarkably witty writing, and Svevo still has his way of making you root for Zeno despite his obvious flaws, but the fragmentary nature of these pieces and the open plot contradictions between them (clearly, Svevo was trying a few different approaches, and at the time of his death, hadn’t fully decided on which path to take) make it a less satisfying read than &lt;em&gt;Zeno’s Conscience&lt;/em&gt;.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Revitalizing Endangered Languages: A Practical Guide&lt;/i&gt; – ed. Justyna Olko, Julia Sallabank&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Revitalizing Endangered Languages&lt;/em&gt; is a unique and much-needed addition to the literature on language revitalization – a volume that combines academic rigor with practical advice, sprinkled throughout with real-life examples from revitalization projects worldwide. The scope is comprehensive, covering everything from sociolinguistics to grant writing. Particularly interesting to me were the sections in the beginning on planning revitalization projects and setting goals – much of the existing literature seems to come with pre-existing notions of what the goals of revitalization are, and it was interesting to see this book interrogate these notions. I will say, though, that I was not very impressed by the chapter on revitalization technology, which seemed hopelessly old-fashioned.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Jewish Languages from A to Z&lt;/i&gt; – Aaron Rubin and Lily Kahn&lt;/div&gt;

&lt;p&gt;I was heartened to see a comprehensive look into Jewish languages - delving far deeper than just Hebrew and Yiddish – aimed at a lay audience. As far as I can tell, this is a condensed and simplified summary of Rubin and Kahn’s earlier edited volume, the &lt;em&gt;Brill Handbook of Jewish Languages&lt;/em&gt;, comprising longer passages about languages with a significant attested history (Ladino, Judeo-Arabic, etc.) and shorter sections about poorly-attested Jewish languages. Every chapter illustrates some primary sources, often providing a fascinating look into historical Jewish culture in areas as far removed as South India and the island of Curaçao. Unfortunately, I found some linguistic details here to be simplified to the point of being sloppy and sometimes misleading. I was particularly confused by how the authors conflated distinctive Jewish spoken languages (e.g., Judeo-Tat, the various Judeo-Aramaic varieties, etc.) with languages that just happened to have been written once in Hebrew (e.g., “Judeo-Maltese”), leading to (in my eyes) a far too broad definition of “Jewish languages.”&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Smuggler's Cove: Exotic Cocktails, Rum, and the Cult of Tiki&lt;/i&gt; – Martin and Rebecca Cate&lt;/div&gt;

&lt;p&gt;About as well-written and nicely laid-out a book as can be made about tropical cocktails and the culture around them. In contrast to Jeff Berry’s &lt;em&gt;Sippin’ Safari&lt;/em&gt;, which is an excellent work of largely primary research but a little confusingly laid out, &lt;em&gt;Smuggler’s Cove&lt;/em&gt; is a very readable synthesis of existing source materials on the history and theory of tropical cocktails and of the Cates’ personal experiences in running an acclaimed tiki bar. The recipes are, of course, top-notch.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Outwitting History: The Amazing Adventures of a Man Who Rescued a Million Yiddish Books&lt;/i&gt; – Aaron Lansky&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Outwitting History&lt;/em&gt; is an enjoyable and fast-paced read about Lansky’s monumental efforts to preserve Yiddish literature starting in the 1970s. Still, I couldn’t shake the feeling that something was not quite genuine about it. I’m sure that Lansky’s account is generally truthful, but some of the anecdotes had such clichéd, Hollywood-y dialogue (especially in the later sections) that it made me question how many of these events really happened the way the book describes them. By the end, it felt a bit like an advertisement for the National Yiddish Book Center (don’t get me wrong, though – I appreciate their work, and they can certainly use such an advertisement).&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Internet Con: How to Seize the Means of Computation&lt;/i&gt; – Cory Doctorow&lt;/div&gt;

&lt;p&gt;&lt;em&gt;The Internet Con&lt;/em&gt; is a long essay about the virtues of interoperability as a way to curb the power of the tech industry. It was well-written and well-argued, with some clever anecdotes, but it didn’t leave much of an impression overall.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Island of the Day Before&lt;/i&gt; – Umberto Eco (tr William Weaver)&lt;/div&gt;

&lt;p&gt;As someone who loved &lt;em&gt;The Name of the Rose&lt;/em&gt; and &lt;em&gt;Baudolino&lt;/em&gt;, reading &lt;em&gt;The Island of the Day&lt;/em&gt; Before was my biggest literary disappointment of the year. Eco set out to write a novel where the universe functions according to 17th-century conceptions of science, and this aspect he accomplishes remarkably well. There’s no denying that it’s a thought-provoking book. The problem is that this may have come at the expense of some of the novel itself. Most of it (spoiler alert) takes place with the main character all alone, and long stretches of the story occur entirely in his head. We get some of the intrigue of &lt;em&gt;The Name of the Rose&lt;/em&gt; and some of the colorful exploration of &lt;em&gt;Baudolino&lt;/em&gt;, but not nearly enough of either to support the length of this novel. By the end, reading it felt like a chore.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Users&lt;/i&gt; – Colin Winnette&lt;/div&gt;

&lt;p&gt;I picked up Users after attending a reading of one chapter of the novel. Unfortunately, the passage I heard was one of the liveliest, best-written parts, and the rest of the book was somewhat of a letdown after that. The premise is timely (it’s all about the unintended consequences of technology), but most of the characters felt more like sketches than fully fleshed-out people, and I was not entirely convinced by the direction the plot went in. I’m still waiting for a great satire of the modern tech industry, but this wasn’t it for me.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Library of the Villa dei Papiri at Herculaneum&lt;/i&gt; – David Sider&lt;/div&gt;

&lt;p&gt;A comprehensive, if bone-dry, examination of the history and contents of the charred papyri excavated from the Villa dei Papiri that I read while following the “Vesuvius Challenge.” My biggest takeaway was that far more information has already been decoded from the papyri using traditional methods than I’d expected.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Final Solution: A Story of Detection&lt;/i&gt; – Michael Chabon&lt;/div&gt;

&lt;p&gt;The premise – a Sherlock Holmes mystery set during the Holocaust – was intriguing enough to pick it up, but, though elegantly written, it felt far too short and lacking in substance, and certainly not much of a detective story at all. A shame, as I’d really enjoyed Chabon’s other novella, &lt;em&gt;Gentlemen of the Road&lt;/em&gt;.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Buildling a DIY Tiki Window for a Home Tiki Bar</title>
   
    <link href="http://alex.nisnevich.com/blog/2023/09/05/building_a_tiki_window.html"/>
   
   <updated>2023-09-05T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2023/09/05/building_a_tiki_window</id>
   
    <content type="html">&lt;p&gt;&lt;em&gt;(Thanks to Annie and Asali for all your help on this project!)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I set out to build a Trader Sam-style “tiki window” for our home tiki bar. You know, this kind of thing, that no home tiki bar seems to be complete without – a screen behind a bamboo façade showing animated tropical imagery:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;https://i.redd.it/plbfldmj2at91.jpg&quot; style=&quot;width: 100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I perused &lt;a href=&quot;https://www.reddit.com/r/Tiki/comments/ytb2fr/diy_trader_sams_window/&quot;&gt;some Reddit threads&lt;/a&gt; and saw a &lt;a href=&quot;https://www.youtube.com/watch?v=E0efUNJxK_c&quot;&gt;brief but helpful Youtube how-to video&lt;/a&gt; but couldn’t really find a complete step-by-step guide, so I ended up largely making things up as I went along.&lt;/p&gt;

&lt;p&gt;Here’s how I did it.&lt;/p&gt;

&lt;h1 id=&quot;step-1-the-screen&quot;&gt;Step 1: The Screen&lt;/h1&gt;

&lt;p&gt;“Digital picture frames” are a thing but all the ones I found online were either too small or too expensive for this project. I didn’t really want to buy a brand-new monitor for this, so instead I went to the legendary &lt;a href=&quot;https://urbanore.com/&quot;&gt;Urban Ore&lt;/a&gt; and found a decade-old &lt;a href=&quot;https://www.displayspecifications.com/en/model/354b16c3&quot;&gt;23” Acer monitor&lt;/a&gt; for $20. It was missing its power adapter, but no problem – I managed to find a &lt;a href=&quot;https://www.amazon.com/dp/B08LRZGD2M&quot;&gt;compatible adapter&lt;/a&gt; online.&lt;/p&gt;

&lt;p&gt;After testing that the monitor actually worked, I pried off its base and set out to build an enclosure for it.&lt;/p&gt;

&lt;h1 id=&quot;step-2-the-housing&quot;&gt;Step 2: The Housing&lt;/h1&gt;

&lt;p&gt;The monitor measured about 2” thick at its thickest points, so I figured I could build a reasonable enclosure out of 2x4 boards (for an nominal depth of 4”, so an actual depth of 3½”), giving me enough wiggle room to fit the monitor and any other electronics I would need inside.&lt;/p&gt;

&lt;p&gt;I cut the boards to size, double-checked that the monitor would fit (before it was too late!):&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;https://i.imgur.com/uxmtSgu.jpg&quot; style=&quot;width: 100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;and attached them together with pocket screws and Gorilla wood glue:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;https://i.imgur.com/XzrzNTP.jpg&quot; style=&quot;width: 100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Once the frame of 2x4s was assembled, I made a “lip” on the inside out of ½”-square dowels, so that the monitor wouldn’t be able to slide forward once placed, and covered those dowels with some rope-shaped trim, just using wood glue for attachment throughout:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;https://i.imgur.com/LudtrSI.jpg&quot; style=&quot;width: 100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;https://i.imgur.com/xu5md0C.jpg&quot; style=&quot;width: 100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I also made a 1¼”-diameter round hole at the center of one of the long sides of the frame, for running a power cord through.&lt;/p&gt;

&lt;p&gt;Now it’s time to fit the monitor inside!&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;https://i.imgur.com/IiXLMXR.jpg&quot; style=&quot;width: 100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The monitor is kept in place with some additional dowel pieces at the back of it (not pictured).&lt;/p&gt;

&lt;h1 id=&quot;step-3-the-bamboo&quot;&gt;Step 3: The Bamboo&lt;/h1&gt;

&lt;p&gt;I was fortunate to have some &lt;a href=&quot;https://foreverbamboo.com/natural-bamboo-slats-1-75-x-72-x-0-25-25-pack/&quot;&gt;1¾”-thick bamboo slats from Forever Bamboo&lt;/a&gt; lying around from another project. These slats are perfectly sized for this housing – exactly two of them fit side-by-side on each 3½”-thick edge side. On the front, the 1¾”-thick slats don’t quite reach the end of the dowel lip (it’s 1½”+½” thick), but still form a nice effect with the rope trim underneath.&lt;/p&gt;

&lt;p&gt;I was a little paranoid about using a nail gun this close to the screen, so we just attached the slats the old-fashioned way, with lots of wood glue. First on the front (using a miter box for the 45° cuts):&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;https://i.imgur.com/6t45Zw9.jpg&quot; style=&quot;width: 100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And then the sides:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;https://i.imgur.com/rPd9wBs.jpg&quot; style=&quot;width: 100%&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;step-4-the-rest-of-the-electronics&quot;&gt;Step 4: The Rest of the Electronics&lt;/h1&gt;

&lt;p&gt;So now that we have a monitor in an enclosure, how do we want to actually play things on it?&lt;/p&gt;

&lt;p&gt;I didn’t really want to put an Internet-connected streaming player inside it (wanted to go a little more low-tech for this), so instead I got a &lt;a href=&quot;https://www.amazon.com/dp/B089VXSD1H&quot;&gt;NEUMI Atom 4K digital media player&lt;/a&gt; and loaded a flash drive with videos that I obtained using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yt-dlp&lt;/code&gt; and then &lt;a href=&quot;https://handbrake.fr/&quot;&gt;Handbrake&lt;/a&gt; to convert to H.264. (For what it’s worth, I had to test out a few different HDMI media players and the NEUMI Atom 4K was the only one that was able to handle the 8-hour-long H.264 videos that I threw at it.)&lt;/p&gt;

&lt;p&gt;You can kind of see how the back of it all looks here:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;https://i.imgur.com/pcSwiLo.jpg&quot; style=&quot;width: 100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s really pretty simple – all we have is the monitor itself with its (replacement) power adaptor, the HDMI player with a tiny USB stick inserted, both plugged into a power strip at the top of the box (this is mainly because I liked the aesthetics of having a single cord coming out of this thing – but I also had to bring in a 6” extension cord because otherwise I couldn’t get the akwardly-shaped power brick for the HDMI player to fit within the confines of the box).&lt;/p&gt;

&lt;p&gt;Note that I positioned the HDMI player precisely with its IR receiver directly over the hole at the bottom of the enclosure – so that I can control it (change videos, etc) by pointing the remote at the bottom hole while it’s operating.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Oh, what’s that piece of paper doing there underneath the monitor, you may ask?&lt;/em&gt; Well, one thing that I didn’t really think about until it was too late is that the power button for the monitor is … at the bottom of it. So the way that I installed it, the monitor was always resting on that button (actually, all of the buttons). Fortunately there was a little bit of wiggle room, so I elevated it just a bit with that sheet of paper – just enough so that the power button isn’t being pressed constantly. And I now have a very janky mechanism for turning the monitor itself on or off – take out the piece of paper and lift and then release the monitor, hitting the power button once. (Fortunately I rarely have to do this, as I have a footswitch connected to the power strip itself that I use to control power to the monitor and media player simultaneously.)&lt;/p&gt;

&lt;h1 id=&quot;step-5-mounting&quot;&gt;Step 5: Mounting&lt;/h1&gt;

&lt;p&gt;With the tiki window built, the last thing I had to worry about was mounting it. The whole thing, wood and electronics at all, only weighs something like 20–25 lbs, but I didn’t want to take any chances. I installed a 1x2 cross-bar (you can see it in the photo above) and attached a &lt;a href=&quot;https://www.homedepot.com/p/OOK-200-lbs-French-Cleat-Picture-Hanger-with-Wall-Dog-Mounting-Screws-1-Pack-55316/202341629&quot;&gt;French cleat&lt;/a&gt; rated at 200 lbs to make sure this thing is really going nowhere.&lt;/p&gt;

&lt;p&gt;I hung it up in our little tiki nook, and &lt;em&gt;voila!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;https://i.imgur.com/u1dEB8S.jpg&quot; style=&quot;width: 100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Coming from essentially zero woodworking experience, this was a bit of an intimidating project to try to take on, but we managed to pull it off pretty much without a hitch. It’s become a centerpiece of our little tiki bar and has definitely inspired me to get more into creative DIY projects – we’ll see where that goes!&lt;/p&gt;

</content>
   
 </entry>
 
 <entry>
   <title>"Lingua o Dialetto?" Exploring Perceptions of Regional Italian Languages Over Time</title>
   
    <link href="https://nbviewer.org/github/AlexNisnevich/lingua-o-dialetto/blob/main/lingua-o-dialetto.ipynb"/>
   
   <updated>2023-08-05T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2023/08/05/lingua_o_dialetto</id>
   
    <content type="html">See https://nbviewer.org/github/AlexNisnevich/lingua-o-dialetto/blob/main/lingua-o-dialetto.ipynb</content>
   
 </entry>
 
 <entry>
   <title>Reading List - 2022</title>
   
    <link href="http://alex.nisnevich.com/blog/2023/01/10/reading_list_2022.html"/>
   
   <updated>2023-01-10T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2023/01/10/reading_list_2022</id>
   
    <content type="html">&lt;p&gt;This was an unusually hectic year, so this set of reviews will be briefer than usual. Flash reviews, if you will.&lt;/p&gt;

&lt;h3&gt;★★★★½&lt;/h3&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;Independent People&lt;/i&gt; – Halldór Laxness [tr. Brad Leithauser]&lt;/div&gt;
&lt;p&gt;Perhaps the whole of the human experience is contained within this epic about a small-time farmer’s rise and fall, brimming with wit and tragedy.&lt;/p&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;Embassytown&lt;/i&gt; – China Mieville&lt;/div&gt;
&lt;p&gt;The perfect novel about language and language contact, though it’s so much more than just that – one of the most inventive pieces of sci-fi I’ve ever read.&lt;/p&gt;

&lt;h3 id=&quot;-1&quot;&gt;★★★★&lt;/h3&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;Imbibe! [revised edition]&lt;/i&gt; – David Wondrich&lt;/div&gt;
&lt;p&gt;Wondrich’s meticulous research and engaging writing take what could be a dull subject – 19th-century cocktail recipes – and make it immensely satifying to read about.&lt;br /&gt; &lt;i&gt;Note: I’ve started a project to recreate each drink in the book, as close to the original recipe as possible, though &lt;a href=&quot;https://instagram.com/aleximbibes&quot; target=&quot;_blank&quot;&gt;I haven’t gotten very far yet&lt;/a&gt;.&lt;/i&gt;&lt;/p&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;Debt: The First 5000 Years&lt;/i&gt; – David Graeber&lt;/div&gt;
&lt;p&gt;I’m not sure if I am fully convinced of Graeber’s theory of debt preceding money and the constant realignment between the two, but this is a fascinating read regardless, with the earliest sections, on social currencies and “everyday communism”, being the most thought-provoking.&lt;/p&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;The Shaman's Coat&lt;/i&gt; – Anna Reid&lt;/div&gt;
&lt;p&gt;I’m a sucker for all things North Asia, so how could I resist Reid’s account of her travels in early 2000s Siberia in search of extant shamanic customs? The history aspect of it is definitely no match for Forsyth’s magisterial &lt;i&gt;A History of the Peoples of Siberia&lt;/i&gt;, but the personal travelogue is an engaging read.&lt;/p&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;How to Do Nothing&lt;/i&gt; – Jenny Odell&lt;/div&gt;
&lt;p&gt;The anti-self-help self-help book, &lt;em&gt;How to Do Nothing&lt;/em&gt; is a manifesto for regaining attention in an age of addictive technology, and manages to be both beautifully written and full of actionable ideas.&lt;/p&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;The Chukchi Bible&lt;/i&gt; – Rytgėv (Yuri Rytkheu) [tr. Ilona Yazhbin Chavasse]&lt;/div&gt;
&lt;p&gt;Part mythology, part poetic ethnography, part adventure story, part family lore, The Chukchi Bible is a mysterious and beautifully written ode to a fading culture.&lt;/p&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;The Dispossessed&lt;/i&gt; – Ursula K. Le Guin&lt;/div&gt;
&lt;p&gt;I don’t know why it’s taken me this long to read this classic of science fiction, with an extraordinary portrayal of an anarchist “ambiguous utopia”. I just wish the whole thing didn’t unravel with an unsatisfying &lt;i&gt;deus ex machina&lt;/i&gt; ending.&lt;/p&gt;

&lt;h3 id=&quot;-2&quot;&gt;★★★½&lt;/h3&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;Why Fish Don't Exist&lt;/i&gt; – Lulu Miller&lt;/div&gt;
&lt;p&gt;Without spoiling too much, I will say that this is a brilliantly written account of one man’s quixotic scientific journey and the lengths that he would go to to achieve his vision, interspersed with a touching personal memoir.&lt;/p&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;Sippin' Safari [10th anniversary edition]&lt;/i&gt; – Jeff &quot;Beachbum&quot; Berry&lt;/div&gt;
&lt;p&gt;What Wondrich does for classic American cocktails, Berry does for tiki. &lt;em&gt;Sippin’ Safari&lt;/em&gt; is a fun romp through the world of tropical drinks, but I found the structure of it to be a little unfocused, lacking a clear through-line.&lt;/p&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;Telluria&lt;/i&gt; – Vladimir Sorokin [tr. Max Lawton]&lt;/div&gt;
&lt;p&gt;A kaleidoscopic collection of 50 linked micro-stories, all set in a bizarre future of feudal states, human-animal hybrids and super-drugs. I have to give Sorokin credit for his profoundly imaginative world-building, but each chapter being told from a completely new perspective robs it of any sense of real narrative progression.&lt;/p&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;A Sociolinguistic History of Scotland&lt;/i&gt; – Robert McColl Millar&lt;/div&gt;
&lt;p&gt;An expertly written work of sociolinguistics that crams centuries of history of usage of Scotland’s two autochtonous languages – Scots and Gaelic – into a slender tome, ultimately striking a critical yet hopeful tone about the prospect of preserving Scotland’s linguistic heritage.&lt;/p&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;Language and Symbolic Systems&lt;/i&gt; – Yuen Ren Chao&lt;/div&gt;
&lt;p&gt;Why read a 60s linguistics textbook? In part because Chao, a pioneer of Chinese linguistics, writes in beautifully fluid and conversational way, and in part because this book reflects his own interests more than anything else, and so features incredibly forward-thinking chapters on sociolinguistics, information theory, and even language technology.&lt;/p&gt;

&lt;h3 id=&quot;-3&quot;&gt;★★★&lt;/h3&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;Elements of Clojure&lt;/i&gt; – Zachary Tellman&lt;/div&gt;
&lt;p&gt;Unfortunately the more Clojure-heavy parts of the book were lost on me (it’s been a while), but I particularly enjoyed the Names chapter, a deep and philosophically interesting exploration of naming in software.&lt;/p&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;The Truth and Other Stories &lt;/i&gt; – Stanislaw Lem [tr. Antonia Lloyd-Jones]&lt;/div&gt;
&lt;p&gt;This collection of previously-untranslated stories by Lem was hit-and-miss. Highlights: “Invasion from Aldebaran”, “The Friend”, “The Hammer”, “One Hundred and Thirty-Seven Seconds” (the latter particular timely in the age of GPT-3…) .&lt;/p&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;The Winter Queen&lt;/i&gt; – Boris Akunin [tr. Andrew Bromfield]&lt;/div&gt;
&lt;p&gt;A page-turner of a historical mystery-thriller that somehow just doesn’t feel satisfying at the end.&lt;/p&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;Here I Am&lt;/i&gt; – Jonathan Safran Foer&lt;/div&gt;
&lt;p&gt;Safran Foer’s trademark wit is still there, but it just didn’t hit me emotionally the way his first two novels did.&lt;/p&gt;

&lt;div style=&quot;font-size: 1.1em;&quot;&gt;&lt;i&gt;The Routledge Handbook of Language Revitalization&lt;/i&gt; – ed. Leanne Hinton, Leena Huss and Gerald Roche&lt;/div&gt;
&lt;p&gt;What it says on the tin. Part 1 is mostly theoretical essays about topics in language revitalization. Part 2, which I personally found more interestings, consists of myriad case studies from around the world.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Extra! Extra! – «Never Been to a Party» Album Release</title>
   
    <link href="http://alex.nisnevich.com/blog/2022/12/19/extra_extra_never_been_to_a_party_release.html"/>
   
   <updated>2022-12-19T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2022/12/19/extra_extra_never_been_to_a_party_release</id>
   
    <content type="html">&lt;p&gt;My dance-rock band &lt;a href=&quot;http://extraextramusic.com/&quot;&gt;Extra! Extra!&lt;/a&gt; has just released our debut album, &lt;i&gt;Never Been to a Party&lt;/i&gt;, after a year-long recording and mixing process. The album is a blend of new wave, punk, disco, and electronic rock, with influences ranging from LCD Soundsystem to the B-52s to ESG. I think you’ll like it.&lt;/p&gt;

&lt;p&gt;The album is available on Spotify …&lt;/p&gt;

&lt;iframe src=&quot;https://open.spotify.com/embed?uri=spotify:album:7BvRS1yJ5O8JYfuqGS4LpO&amp;amp;view=coverart&quot; width=&quot;500&quot; height=&quot;380&quot; frameborder=&quot;0&quot; allowtransparency=&quot;true&quot; allow=&quot;encrypted-media&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;… as well as &lt;a href=&quot;https://extraextramusic.bandcamp.com/album/never-been-to-a-party&quot;&gt;Bandcamp&lt;/a&gt; and most other streaming platforms.&lt;/p&gt;

&lt;p&gt;We will hopefully press it to vinyl next year, if there is demand for it!&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Reading List - 2021</title>
   
    <link href="http://alex.nisnevich.com/blog/2022/01/10/reading_list_2021.html"/>
   
   <updated>2022-01-10T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2022/01/10/reading_list_2021</id>
   
    <content type="html">&lt;h3&gt;★★★★½&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;A History of the Peoples of Siberia: Russia’s North Asian Colony 1581-1990&lt;/em&gt; – James Forsyth&lt;/strong&gt;&lt;br /&gt;
Ah, just the book I needed to feed my obsession with Siberia and The Far East. In fact, it’s so comprehensive and well-researched that I was surprised to even find a book like this in English. Forsyth’s writing can be dry at times but he admirably compiles and presents an overwhelming wealth of information about the indigenous people of North Asia, from the Khanty and Mansi along the Ob river all the way to the Chukchi and Itelmens of the northeast edge. He presents some cultural background, but for the most part this is a history of Russian exploitation, with roughly the first half of the book following the tsarist colonial project and the second half of the book follows the varying rises and falls (but mostly the latter) in the fortunes of the Siberian peoples through the Soviet era. It’s a shame that the story ends in 1990 and the author never put out a new edition, because I haven’t yet been able to find a good follow-up read on contemporary Siberian indigenous history. This is a fascinating and tragic account of a group of peoples that most Western books about Russia barely even mention.
&lt;em&gt;[File under History / North Asia.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Seeing Like a State: How Certain Schemes to Improve the Human Condition Have Failed&lt;/em&gt; – James Scott&lt;/strong&gt;&lt;br /&gt;
Few books have influenced my thinking as much as &lt;em&gt;Seeing Like a State&lt;/em&gt; has. Scott’s central thesis is that a diverse range of state activities over the past few centuries can be seen through the lens of &lt;em&gt;legibility&lt;/em&gt; - a state, when faced with a complicated, local, difficult-to-understand situation will attempt to standardize and homogenize, sacrificing local knowledge and diversity for the sake of simplification. The introductory example here is 18th century Prussian “scientific forestry”, where natural forests, which formed complete ecosystems but whose timber output was difficult to quantify and predict, were replaced by rows upon rows of identical trees, which were more &lt;em&gt;legible&lt;/em&gt; in the sense of being easier to reason about as a system, but of course were much less resilient against weather and pests than the natural, “illegible” forest system that they replaced. From the natural realm, Scott continues to a variety of examples of state simplification of human systems, from Le Corbusier’s high-modernist city planning to Soviet collectivization to Tanzanian “villagization” in the 1960s and 70s, finally closing with a meditation on his idea of &lt;em&gt;“mētis”&lt;/em&gt;, the kind of crucial, hyper-specific local knowledge that gets lost during these instances of standardization. Overall, this was an engaging and very illuminating read, marred for me only by a chapter contrasting Lenin and Luxembourg’s visions of revolution that seemed particularly weak / out-of-place here.
&lt;em&gt;[File under Political Science or Anthropology.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;East of Eden&lt;/em&gt; – John Steinbeck&lt;/strong&gt;&lt;br /&gt;
It’s a classic for a reason. The writing is beautiful (Steinbeck referred to it as his “first novel” after he finished it, and it really does show an incredible maturity of style), but at the same time it feels like it’s missing some of the humanism, the generalized love and compassion for humanity, that is so present in his earlier novels like &lt;em&gt;The Grapes of Wrath&lt;/em&gt;, &lt;em&gt;Cannery Row&lt;/em&gt;, etc. The political interjections feel out-of-place and, often, out-of-touch, as does a shockingly racist passage near the very beginning. The Biblical allegory is about as heavy-handed as one could expect from the title. One major character is so cartoonishly evil that it’s difficult to feel invested in her storyline. Structurally, it’s all over the place, with some particularly meandering plot elements, and a narrative voice that switches between third-person and first-person when you least expect it. And yet, somehow it just &lt;em&gt;works&lt;/em&gt;. I didn’t know quite how I felt about &lt;em&gt;East of Eden&lt;/em&gt; until the very end, which was one of my most emotional reading experiences in recent memory.
&lt;em&gt;[File under Fiction / Literary.]&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;-1&quot;&gt;★★★★&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;A Swim in a Pond in the Rain: In Which Four Russians Give a Master Class on Writing, Reading, and Life&lt;/em&gt; – George Saunders&lt;/strong&gt;&lt;br /&gt;
The stories are, of course, a pleasure to read, especially Tolstoy’s &lt;em&gt;“Master and Man”&lt;/em&gt;, Gogol’s &lt;em&gt;“The Nose”&lt;/em&gt;, Turgenev’s &lt;em&gt;“The Singers”&lt;/em&gt; … But what I didn’t expect was just how illuminating Saunders’s commentary is to each story, and how much I learned about what goes into crafting a good short story. I’m not a writer, but a lot of the exercises and approaches here apply just as well to critically reading and enjoying fiction, and I came out of this book with a deeper appreciation of Russian literature and the short story in general.
&lt;em&gt;[File under Fiction / Literary / Short stories but also Literary Criticism.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Flutes of Fire: Essays on California Indian Languages&lt;/em&gt; – Leanne Hinton&lt;/strong&gt;&lt;br /&gt;
&lt;em&gt;(Something of a re-read – I’ve read part of it before for a class in undergrad, but never read it cover-to-cover before.)&lt;/em&gt;
This book feels like something of a rarity – a broad-strokes look at indigenous languages of California that’s accessible to the layperson while still being linguistically rigorous and not “dumbing down” the subject matter. And it’s simply a delight to read, as you can feel Hinton’s sense of wonder about the languages and language features that she’s writing about. This is the book that originally kindled my interest in linguistics when I read part of it in an introductory college course, and reading it now brings that feeling back. The last two sections, “Language and Dominion” and “Keeping the Languages Alive”, are ones that I don’t think I’ve read before and they feel particularly important now, though it’s sad to think how much has likely been lost since this book was first published in 1994.
&lt;em&gt;[File under Linguistics / Areal / North America.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Linguistic Diversity in Space and Time&lt;/em&gt; – Johanna Nichols&lt;/strong&gt;&lt;br /&gt;
There’s a lot going on here, even for a reader like me with only an undergraduate grasp of linguistics. Nichols (1) assesses a set of especially “stable” language features for nearly 200 languages covering most of the world’s language families and isolates, building probably the largest database of language typology of its time; (2) presents a novel way to trace the stability of each of these features over space and time; and (3) from there, develops a theory of language diversity that both suggests answers to long-standing puzzles &lt;em&gt;(e.g. Q: Why are there so many more distinct language families in the Americas than in the “Old World”? A: The typological properties common in American languages are exactly the ones that cause the linguistic data necessary for the comparative method to “erode” away more quickly.)&lt;/em&gt; and raises new ones &lt;em&gt;(e.g. Nichols’s data suggests that the Americas were initially populated largely by people coming from the Australian/Papuan direction, which certainly isn’t a mainstream theory)&lt;/em&gt;. This is a truly monumental project, that easily could have ended up as multiple important books, and it is wild to see so much ambition and insight packed into a single, fairly slim, volume.
&lt;em&gt;[File under Linguistics / Typology.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Bullshit Jobs: A Theory&lt;/em&gt; – David Graeber&lt;/strong&gt;&lt;br /&gt;
At one point, Graeber describes the job of a cultural anthropologist to be &lt;em&gt;(paraphrasing)&lt;/em&gt; digging deeply into cultural assumptions that are taken at face value, and &lt;em&gt;Bullshit Jobs&lt;/em&gt; does just that. It examines a concept that doesn’t even feel worthy of writing a book about (“many jobs feel boring and meaningless” is hardly news!), and uses it as a lens to examine the bureaucratization of the economy, the history of popular perception of capitalism, and some surprising holdovers from the age of feudalism that seem to reassert themselves in today’s “bullshit” jobs. And unlike many such books that present series social issues without presenting much in the way of a remedy, Graeber ends with a decent argument in favor of universal basic income as at least a partial solution here.
&lt;em&gt;[File under Anthropology / Cultural.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Dune&lt;/em&gt; – Frank Herbert&lt;/strong&gt;&lt;br /&gt;
&lt;em&gt;(Technically a re-read, but I haven’t read it since I was in high school, so I’ll count it.)&lt;/em&gt;
Re-reading &lt;em&gt;Dune&lt;/em&gt; (for the obvious reasons) was an interesting experience because, while I remembered the overall plot outline, one thing that slipped my memory was just how &lt;em&gt;weird&lt;/em&gt; of a book &lt;em&gt;Dune&lt;/em&gt; is, and I mean that in the best possible way. Even compared to its New Wave science fiction contemporaries, &lt;em&gt;Dune&lt;/em&gt; is really its own thing, spending seemingly more time speculating about desert ecology, religion, and the spread of ideas than on, you know, the action itself, much of which ends up taking place “off-screen”. The plot itself feels less engaging in the second half of the book, but Herbert can be forgiven for this because everything else is still so interesting.
&lt;em&gt;[File under Fiction / Speculative.]&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;-2&quot;&gt;★★★½&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Manhattan Beach&lt;/em&gt; – Jennifer Egan&lt;/strong&gt;&lt;br /&gt;
A beautifully-written novel with a lot of passages that I really liked, that unfortunately seemed to sputter out partway through, with too much time spent following a gangster subplot that just felt like one cliché after another. Egan spent over a decade putting &lt;em&gt;Manhattan Beach&lt;/em&gt; together, and her meticulous attention to period detail is clear, but in the process the novel ends up not having the same kind of wild spark that &lt;em&gt;A Visit From the Goon Squad&lt;/em&gt; had.
&lt;em&gt;[File under Fiction / Historical.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;We Should All Be Feminists&lt;/em&gt; – Chimamanda Ngozi Adichie&lt;/strong&gt;&lt;br /&gt;
Adichie makes her point clearly and elegantly. It’s a very short read, but perhaps is just the length it needs to be.
&lt;em&gt;[File under Essays.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Weapons of Math Destruction: How Big Data Increases Inequality and Threatens Democracy&lt;/em&gt; – Cathy O’Neil&lt;/strong&gt;&lt;br /&gt;
A well-written and timely analysis of the dangers of the over-reliance on ML to do things like score résumés and process loan applications. I feel like there’s not too much here that people in the ML field haven’t been warning about for years, but it’s important that this issue gets broader attention, and this book does a great job of clearly explaining the issue to a wide audience.
&lt;em&gt;[File under Technology / Social Aspects]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;The Chess Garden: Or, The Twilight Letters of Gustav Uyterhoeven&lt;/em&gt; – Brooks Hansen&lt;/strong&gt;&lt;br /&gt;
This might not be my &lt;em&gt;favorite&lt;/em&gt; novel I read this year, but it is certainly one that will stick in my memory for a long time. I don’t even really know how to describe it – a colorful biography of the (fictional) Dr. Uyterhoeven, interspersed with his stories of a journey through a land populated by anthropomorphic board game pieces, which in turn form allegories about … the conflict between rationalism and “vitalism”? This is a profoundly ambitious novel that comes very close to something special. It does end up feeling tedious near the middle, and the ending was a little too overtly “spiritual” for my taste. But nonetheless, I have no regrets about opening up this book.
&lt;em&gt;[File under Fiction / Experimental.]&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;-3&quot;&gt;★★½&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Bay Area Cocktails: A History of Culture, Community and Craft&lt;/em&gt; – Shanna Farrell&lt;/strong&gt;&lt;br /&gt;
Farrell has certainly done her research, conducting dozens of interviews over several years to put together this book. And it’s full of interesting tidbits about the various characters behind the Bay Area’s cocktail renaissance. What feels missing is a discussion of just what makes the Bay Area special (if anything!) - most of the trends that the author traces in cocktail history have little to do with this particular region, and as a result the book feels aimless at times.
&lt;em&gt;[File under Food &amp;amp; Drink / Cocktails / History.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Reality and Other Stories&lt;/em&gt; – John Lancaster&lt;/strong&gt;&lt;br /&gt;
“Old-fashioned ghost stories about technology” is a premise that I very much wanted to like, but unfortunately Lancaster fails to deliver with most of these. Most of them feel a little too clever, as though they’re written by someone who wants to write “genre fiction” but is feeling just a bit too smug about it. The best story in the collection, “Reality”, is also the one that is the furthest away from that overall premise.
&lt;em&gt;[File under Fiction / Speculative / Short stories.]&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;-4&quot;&gt;★½&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Gem of the Lost Coast: A Narrative History of Shelter Cove&lt;/em&gt; – Mario Machi&lt;/strong&gt;&lt;br /&gt;
A passably-written collection of local history of the Shelter Cove area, written by an active participant in that history.
&lt;em&gt;[File under History / California / Local.]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Native California Hero’s&lt;/em&gt; [sic] &lt;em&gt;of the Miwok Confederation: Teleguac, Estanislas and Yolosko&lt;/em&gt; – Guy (Redcorn) Nixon&lt;/strong&gt;&lt;br /&gt;
I was hoping to read some interesting and previously undocumented bits of Miwok history taken from oral interviews conducted by the author. Unfortunately, the end result is so poorly written as to be largely unreadable. The opening chapters, on the ecological aspects of Native Californians’ first interactions with settlers, raises some interesting questions but are difficult to take at face value.
&lt;em&gt;[File under History / California / Native American.]&lt;/em&gt;&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Reading List - 2020</title>
   
    <link href="http://alex.nisnevich.com/blog/2021/01/09/reading_list_2020.html"/>
   
   <updated>2021-01-09T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2021/01/09/reading_list_2020</id>
   
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://www.facebook.com/alex.nisnevich/posts/10157848567891828&quot;&gt;&lt;i&gt;[Originally posted on Facebook]&lt;/i&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every January, I post short reviews of the books I read the previous year. Due to the craziness of the past year, I didn’t have the energy to actually write reviews, but here’s what I read in 2020:&lt;/p&gt;

&lt;h3&gt;★★★★★&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;One Hundred Years of Solitude&lt;/em&gt; – Gabriel García Márquez (tr. Gregory Rabassa)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-1&quot;&gt;★★★★½&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Utopia Drive: A Road Trip Through America’s Most Radical Idea&lt;/em&gt; – Eric Reese&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;The Last Samurai&lt;/em&gt; – Helen DeWitt&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Exhalation: Stories&lt;/em&gt; – Ted Chiang&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Piranesi&lt;/em&gt; – Suzanna Clarke&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-2&quot;&gt;★★★★&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;The Shock Doctrine&lt;/em&gt; – Naomi Klein [thanks Mel]&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;So You Want to Talk About Race&lt;/em&gt; – Ijeoma Oluo&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;The Color of Law: A Forgotten History of How Our Government Segregated America&lt;/em&gt; – Richard Rothstein&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Stories of Your Life and Others&lt;/em&gt; – Ted Chiang&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Lame Fate / Ugly Swans&lt;/em&gt; – Arkadi and Boris Strugatsky (tr. Maya Vinokour)&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Monday Starts on Saturday&lt;/em&gt; – Arkadi and Boris Strugatsky (new tr. by Andrew Bromfield)&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;October: The Story of the Russian Revolution&lt;/em&gt; – China Mieville&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-3&quot;&gt;★★★½&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Trysting&lt;/em&gt; – Emmanuelle Pagano (tr. Jennifer Higgins and Sophie Lewis)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-4&quot;&gt;★★★&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;The Tall Building Artistically Reconsidered&lt;/em&gt; – Ada Louise Huxtable&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Lingo: Around Europe in Sixty Languages&lt;/em&gt; – Gaston Dorren (tr. Alison Edwards)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-5&quot;&gt;★★½&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;The Mathematician’s Shiva&lt;/em&gt; – Stuart Rojstaczer&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-6&quot;&gt;★★&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Give them an Argument: Logic for the Left&lt;/em&gt; – Ben Burgis&lt;/li&gt;
&lt;/ul&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Migrating a ASP.NET Application from SQL Server to MySQL</title>
   
    <link href="http://alex.nisnevich.com/blog/2020/04/18/migrating_asp_net_mysql.html"/>
   
   <updated>2020-04-18T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2020/04/18/migrating_asp_net_mysql</id>
   
    <content type="html">&lt;p&gt;Last weekend I had the unenviable task of moving a &lt;a href=&quot;http://fourkingschess.com/&quot;&gt;legacy ASP.NET application&lt;/a&gt; to a new server, and as part of the move I had to migrate the application’s database from SQL Server to MySQL, the only SQL flavor supported by the new server.&lt;/p&gt;

&lt;p&gt;What I thought would be a fairly routine switchover turned out to be a rather involved couple of days, and none of the resources I could find online were able to illuminate the whole process for me. So I’m writing the post I wish I could have read before embarking on this adventure.&lt;/p&gt;

&lt;p&gt;Here’s what I did:&lt;/p&gt;

&lt;h3 id=&quot;step-1-copy-over-the-database&quot;&gt;Step 1. Copy over the Database&lt;/h3&gt;
&lt;p&gt;To copy over the data from the old SQL Server database to the new MySQL database, I used &lt;a href=&quot;https://www.mysql.com/products/workbench/migrate/&quot;&gt;MySQL Workbench’s Migration Wizard&lt;/a&gt;, connecting to the old database through a SQL Server ODBC driver.&lt;/p&gt;

&lt;p&gt;One would hope that this would be an automatic process, but unfortunately, it didn’t quite work without manual intervention, for a few reasons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The SQL generated by the Migration Wizard to recreate the tables failed with a cryptic “Error Code 1005. Can’t create table”, giving “Specified key was too long; max key length is 767 bytes” as he reason. It ended up being an encoding issue – for whatever reason, the generated SQL assigned an unusual character encoding to each field. This issue went away after I removed all of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ENCODING&lt;/code&gt; clauses in the generated SQL and re-ran it.&lt;/li&gt;
  &lt;li&gt;Timestamp literals (e.g. in default values for TIMESTAMP fields) were not correct in the generated SQL, I assume because of differences in how dates and times are represented in SQL Server vs MySQL. I had to fix these by hand.&lt;/li&gt;
  &lt;li&gt;After all this, some indices and constraints still got messed up, and needed to be manually fixed before copying over the data from the original database.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;step-2-install-new-packages&quot;&gt;Step 2. Install New Packages&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.nuget.org/&quot;&gt;NuGet&lt;/a&gt; wasn’t really a thing when I first created this application (in 2011), so I wasn’t looking forward to this step, but I was pleasantly surprised by how easy NuGet is to use and how solid the Visual Studio integration for it is.&lt;/p&gt;

&lt;p&gt;One thing that was a little tricky was that my application uses .NET Framework 4.0 (and I’ve been unable to upgrade it to 4.5+ for a variety of reasons), so I couldn’t use any of the last fewpain versions of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MySql.Data&lt;/code&gt; (both v8 and v6.10 require .NET Framework 4.5). It took some trial and error, but I ended up installing the following packages:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MySql.Data&lt;/code&gt; 6.9.12&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MySql.Data.Entity&lt;/code&gt; 6.9.12&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MySql.Web&lt;/code&gt; 6.9.12 (for membership, see Step 4 below)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WebMatrix.WebData&lt;/code&gt; (for membership, see Step 4 below)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;step-3-configure-mysql-in-webconfig&quot;&gt;Step 3. Configure MySQL in web.config&lt;/h3&gt;
&lt;p&gt;Now that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MySql.Data.MySqlClient&lt;/code&gt; is installed, switching over to it is actually quite painless, and requires no code changes. I made the following changes to my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Web.config&lt;/code&gt; file:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;configuration/connectionStrings&lt;/code&gt;, update all the connection strings to point to the MySQL database and swap out &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;providerName=&quot;System.Data.SqlClient&quot;&lt;/code&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;providerName=&quot;MySql.Data.MySqlClient&quot;&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;system.data/DbProviderFactories&lt;/code&gt;, add the following (this may be automatically added for you by NuGet):&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;add&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MySQL Data Provider&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;invariant=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MySql.Data.MySqlClient&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;description=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.Net Framework Data Provider for MySQL&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.9.12.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Replace the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;entityFramework&lt;/code&gt; section with the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;entityFramework&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;codeConfigurationType=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MySql.Data.Entity.MySqlEFConfiguration, MySql.Data.Entity.EF6&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;defaultConnectionFactory&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MySql.Data.Entity.MySqlConnectionFactory, MySql.Data.Entity.EF6&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;providers&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;provider&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;invariantName=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MySql.Data.MySqlClient&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6, Version=6.9.12.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/provider&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/providers&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/entityFramework&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtime/assemblyBinding&lt;/code&gt;, add the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependentAssembly&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;assemblyIdentity&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MySql.Data&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;publicKeyToken=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;c5687fc88969c44d&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;bindingRedirect&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;oldVersion=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1.0.0.0-6.9.12.0&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;newVersion=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;6.9.12.0&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependentAssembly&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;and-now-&quot;&gt;And now …&lt;/h3&gt;

&lt;p&gt;After I performed steps 1–3, I tried running my application. Everything worked! I tried changing up some values in the MySQL database to make sure that we weren’t still pointing to the old SQL Server database. It picked up the new values! Well, that wasn’t so bad.&lt;/p&gt;

&lt;p&gt;So I shut off SQL Server, reload the page, and …&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;A network-related or instance-specific error occurred &lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;establishing a connection to SQL Server.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Wait, what? We’ve switched over the connection strings to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MySqlClient&lt;/code&gt; – how could we still be trying to access SQL Server after?&lt;/p&gt;

&lt;p&gt;After some disbelief, I finally realized that:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;While testing my changes, I (foolishly) set up the new MySQL database on the same box, with the same database name and with the same admin user credentials as the old SQL Server database (albeit on a different port) – in other words, with an identical connection string, and&lt;/li&gt;
  &lt;li&gt;I’d neglected to make any changes to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;membership&amp;gt;&lt;/code&gt; section of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Web.config&lt;/code&gt;, so the default &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.Web.Security.SqlMembershipProvider&lt;/code&gt; provider was still being used – and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SqlMembershipProvider&lt;/code&gt; &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.web.security.sqlmembershipprovider?view=netframework-4.8&quot;&gt;only supports SQL Server&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, by a unfortunate confluence of events, even though the application was correctly connecting to the new MySQL database via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MySql.Data.MySqlClient&lt;/code&gt;, the membership provider was reading the same connection strings as SQL Server connections and connecting to the SQL Server database that happened to be on the same box, with the same admin user credentials – a SQL Server database that had still existed in my initial tests, before I shut it down!&lt;/p&gt;

&lt;p&gt;If, like me, you’re using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SqlMembershipProvider&lt;/code&gt;, there’s one more step you’ll need to take:&lt;/p&gt;

&lt;h3 id=&quot;step-4-switch-over-to-mysqlmembershipprovider-if-needed&quot;&gt;Step 4. Switch over to MySqlMembershipProvider (if needed)&lt;/h3&gt;

&lt;p&gt;First, add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MySql.Web&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WebMatrix.WebData&lt;/code&gt; packages with NuGet if you haven’t already.&lt;/p&gt;

&lt;p&gt;Then, switch to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MySqlMembershipProvider&lt;/code&gt; in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;membership&amp;gt;&lt;/code&gt; section of your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Web.config&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;membership&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;defaultProvider=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MySQLMembershipProvider&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;membership&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;providers&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;add&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MySQLMembershipProvider&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MySql.Web.Security.MySQLMembershipProvider, MySql.Web, Version=6.9.12.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;connectionStringName=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;...&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/membership&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/membership&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You may also need to configure &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MySQLProfileProvider&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MySQLRoleProvider&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Web.config&lt;/code&gt;, or this may automatically be done for you by the NuGet installation. In any case, I didn’t have to do anything with these.&lt;/p&gt;

&lt;p&gt;After doing this, my application was finally able to start up without crashing (because it no longer was trying to connect to a SQL Server database that no longer existed), but none of the user-related logic was working, as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Membership.GetAllUsers()&lt;/code&gt; would always return an empty collection.&lt;/p&gt;

&lt;p&gt;It turns out that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MySQLMembershipProvider&lt;/code&gt;, sensibly, uses different tables in your database than the default &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SqlMembershipProvider&lt;/code&gt; does: e.g. instead of an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aspnet_users&lt;/code&gt; table it uses a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;my_aspnet_users&lt;/code&gt;, and similarly all other tables are prefixed with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;my_aspnet_&lt;/code&gt;. Migrating the data over from the old &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aspnet_&lt;/code&gt; tables was not completely straightforward either, because the schema is slightly different, with columns renamed and reordered, and in some cases a few columns added. I did some janky SQL-fu to get the user data moved over because I couldn’t find a more automated solution &lt;em&gt;(old StackOverflow threads &lt;a href=&quot;https://stackoverflow.com/questions/2242644/using-mysql-mysqlmembershipprovider-autogenerateschema-true-not-working&quot;&gt;mention a ASP.Net configuration tool&lt;/a&gt; that could help with this user data migration, but it doesn’t seem to exist anymore)&lt;/em&gt;.&lt;/p&gt;

&lt;h1 id=&quot;and-youre-done&quot;&gt;And you’re done!&lt;/h1&gt;

&lt;p&gt;It was a slightly more perilous journey than anticipated, but after following these steps I was able to get my legacy ASP.NET v4.0 application running with MySQL. And now that it’s on a database platform with as rich an ecosystem as MySQL, hopefully I won’t have to do anything like this again.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Reading List - 2019</title>
   
    <link href="http://alex.nisnevich.com/blog/2020/01/10/reading_list_2019.html"/>
   
   <updated>2020-01-10T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2020/01/10/reading_list_2019</id>
   
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://www.facebook.com/notes/alex-nisnevich/reading-list-2019/10156322275831685/&quot;&gt;&lt;i&gt;[Originally posted on Facebook]&lt;/i&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another year, another reading list! Here’s what I read last year.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★★★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Evicted: Poverty and Profit in the American City&lt;/i&gt; – Matthew Desmond&lt;/div&gt;

&lt;p&gt;Even if this were a work of fiction, it would get high marks for its gritty, gripping portrayal of impoverished families struggling to survive in inner-city Milwaukee. The fact that this is all documentary nonfiction makes it all the more remarkable. Desmond doesn’t offer much in the way of solutions, but he viscerally depicts the problem of extreme urban poverty. If every policymaker had to read this book, maybe things could get better. &lt;em&gt;[File under Sociology / Urban.]&lt;/em&gt;&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Death and Life of Great American Cities&lt;/i&gt; – Jane Jacobs&lt;/div&gt;

&lt;p&gt;Jacobs breaks down urban dynamics into axiomatic pieces and applies them masterfully in arguing for what makes a city work (short blocks, mixed uses, mixed building ages, and density) and what doesn’t (modernist planning, essentially). Her analysis has some flaws (for one thing, there’s no mention of gentrification) but has nonetheless aged remarkably well given its fast-moving topic. I can see why this book was such a big deal when it came out, and I can’t believe it took me so long to finally read it. &lt;em&gt;[File under Sociology / Urban.]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Goldfinch&lt;/i&gt; – Donna Tartt&lt;/div&gt;

&lt;p&gt;One of those books you just can’t put down – I think I read the last two parts in one fell swoop. A lot of critics seem to have accused The Goldfinch of being melodramatic, and maybe it is, but what is life without some melodrama? (Admittedly, I came into it already a Donna Tartt fan – &lt;em&gt;The Secret History&lt;/em&gt; is a favorite.) There’s some wonderful portrayals of grief and obsession, but most of all, I just appreciate a story about someone progressively digging themselves into an unthinkably deep hole, and still, against all odds, climbing most of the way out. &lt;em&gt;[File under Fiction / Literary.]&lt;/em&gt;&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Dwarf&lt;/i&gt; – Pär Lagerkvist (tr. Alexandra Dick)&lt;/div&gt;

&lt;p&gt;Warring states, brutal betrayals, and a cunning, misanthropic dwarf. Nope, it’s not &lt;em&gt;Game of Thrones&lt;/em&gt;, but rather Lagerkvist’s bleak novella exploring the nature of evil. &lt;em&gt;[File under Fiction / Literary.]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Samarkand&lt;/i&gt; – Amin Maalouf (tr. Russell Harris)&lt;/div&gt;

&lt;p&gt;Witty and poetic, as a novel about Omar Khayyam ought to be. Maalouf ties two parallel historical threads (one in the 11th century and one in the early 20th century) together in a surprisingly natural way, skillfully juggles fact and fiction, and injects contemporary relevance through an examination of European influence in 20th century Persia. &lt;em&gt;[File under Fiction / Historical.]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Art of Piano Playing&lt;/i&gt; – Heinrich Neuhaus (tr. K. A. Leibovitch)&lt;/div&gt;

&lt;p&gt;Neuhaus’s book, ostensibly about piano performance, is really equal parts playing tips, musings about aesthetics, and anecdotes from his time as a fixture of Moscow’s music scene. Don’t expect a straightforward account, but there are some real gems buried under the constant (but entertaining) digressions. &lt;em&gt;[File under Music / Performance.]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;House of Suns&lt;/i&gt; – Alistair Reynolds&lt;/div&gt;

&lt;p&gt;Reynolds manages to write a story that is both thrilling and moving, despite an unimaginably far-future, high-technology setting. This is “hard” science fiction at its best. &lt;em&gt;[File under Fiction / Speculative.]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;CivilWarLand in Bad Decline&lt;/i&gt; – George Saunders&lt;/div&gt;

&lt;p&gt;Even in his first short-story collection, Saunders shows his unique, darkly satirical voice, but these stories in particular do come off feeling somewhat repetitive, and setting them all in bizarre theme parks doesn’t help. Highlights: “CivilWarLand in Bad Decline”, “Offloading for Mrs. Schwartz”, and the remarkable Afterword in the new edition. &lt;em&gt;[File under Fiction / Satire / Short stories.]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Conquest of Bread&lt;/i&gt; – Piotr Kropotkin (tr. Piotr Kropotkin)&lt;/div&gt;

&lt;p&gt;Kropotkin turns out to be much more straightforward and unpretentious than I’d expected. The Conquest of Bread convincingly lays out an alternative vision for society, while still maintaining nuance and not getting bogged down in ideology (I particularly appreciated the chapter on the necessity of luxuries, such as pianos, even in an anarchist society). &lt;em&gt;[File under Philosophy / Political.]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Classical Style&lt;/i&gt; – Charles Rosen&lt;/div&gt;

&lt;p&gt;The Classical Style takes something I thought I understood (sonata form) and shows it to be both far more complicated and more intuitive than I’d thought. This book was slow going (it took me a few months on-and-off to get through it due to all the musical examples) but completely changed how I listen to classical music, particularly that of Haydn and Mozart. Worth it for anyone looking to learn more about the nuts and bolts of classical music. &lt;em&gt;[File under Music / Analysis.]&lt;/em&gt;&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Murray Bookchin Reader&lt;/i&gt; – Murray Bookchin (ed. Janet Biehl)&lt;/div&gt;

&lt;p&gt;Bookchin had good ideas on communalism, social hierarchy and ecology, but his rhetorical style is dense and not always comprehensible. There were a lot of thought-provoking bits, but they were too often buried by rather abstruse writing filled with not-well-defined terminology.  &lt;em&gt;[File under Philosophy / Political.]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Cloven Viscount&lt;/i&gt; – Italo Calvino (tr. Archibald Colquhoun)&lt;/div&gt;

&lt;p&gt;Not Calvino at his best, but short and sweet, reading almost like an offbeat fairy tale. &lt;em&gt;[File under Fiction / Fantasy.]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Energy and Civilization&lt;/i&gt; – Vaclav Smil&lt;/div&gt;

&lt;p&gt;The first half of the book (prehistoric agriculture until the fossil fuels era) is fantastic and informed a lot about how I think about human prehistory. Everything after that is a tedious slog as Smil seemingly tries to cram in every single invention and development of the Industrial Revolution into his book for the sake of comprehensiveness, without much payoff at the end. I would stop after Chapter 4. &lt;em&gt;[File under History / Energy.]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;A Gentleman in Moscow&lt;/i&gt; – Amor Towles&lt;/div&gt;

&lt;p&gt;A clever premise, hampered by a charming but ultimately dull protagonist and a generally one-dimensional cast (with some notable exceptions). The novel is jam-packed with references to Russian literature, but I didn’t get the sense that Towles actually succeeded in understanding the “Russian mindset”, so to speak. If you’re in the mood to read a contemporary multi-generational epic set largely in the Soviet Union, try Sana Krasikov’s fantastic debut novel The Patriots instead. &lt;em&gt;[File under Fiction / Historical.]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Mysteries of Pittsburgh&lt;/i&gt; – Michael Chabon&lt;/div&gt;

&lt;p&gt;A modern coming-of-age story that’s by turns poignant and hilarious. It definitely has its moments, but the plot feels uneven, with the fantastical gangster subplot seeming particularly out of place. I prefer Chabon’s later works, but it’s interesting to see how he’s grown as a writer. &lt;em&gt;[File under Fiction / Contemporary.]&lt;/em&gt;&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Fear and Loathing in the North: Jews and Muslims in Medieval Scandinavia and the Baltic Region&lt;/i&gt; – ed. Cordelia Heß, Jonathan Adams&lt;/div&gt;

&lt;p&gt;I picked this up on a whim (the Kindle edition was free) and boy, did I end up learning far more than I ever expected about Jews and Muslims in medieval Scandinavia and the Baltic region. There’s some interesting scholarly work here, and it stoked my interest in the Medieval period, leading me to pick up The Cloven Viscount (Calvino), The Dwarf (Lagerkvist), and Samarkand (Malouf). &lt;em&gt;[File under History / Northern Europe.]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;My Boyfriend is a Bear&lt;/i&gt; – Pamela Ribon and Cat Farris&lt;/div&gt;

&lt;p&gt;A cute, quick read. &lt;em&gt;[File under Graphic novels / Humor.]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Killing Commendatore&lt;/i&gt; – Haruki Murakami (tr. Philip Gabriel and Ted Goossen)&lt;/div&gt;

&lt;p&gt;I wanted to like this book, and the painting scenes are some of the best depictions of the artistic process I’ve read in fiction. In the end, though, it felt like a slog, and had none of the payoff that I was hoping for – nothing seemed to get resolved and not much was explained. Even more so than 1Q84, this felt like a knockoff Murakami novel, rather than the real deal. &lt;em&gt;[File under Fiction / Literary.]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Childhood's End&lt;/i&gt; – Arthur C. Clarke&lt;/div&gt;

&lt;p&gt;An interesting subversion of the alien-invasion trope, but the plot is all over the place, jumping from sci-fi to crime drama to utopian exposition to a bizarre ESP subplot that Clarke himself later regretted including. The Strugatsky brothers delivered on this premise better in their works, especially their 1987 novel The Ugly Swans. &lt;em&gt;[File under Fiction / Speculative.]&lt;/em&gt;&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Visualizing Classical Music Influence with networkx</title>
   
    <link href="https://nbviewer.jupyter.org/github/AlexNisnevich/blog/blob/master/_notebooks/music-graphs.ipynb"/>
   
   <updated>2019-02-27T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2019/02/27/visualizing_classical_music_influence</id>
   
    <content type="html">See https://nbviewer.jupyter.org/github/AlexNisnevich/blog/blob/master/_notebooks/music-graphs.ipynb</content>
   
 </entry>
 
 <entry>
   <title>Reading List - 2018</title>
   
    <link href="http://alex.nisnevich.com/blog/2019/01/06/reading_list_2018.html"/>
   
   <updated>2019-01-06T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2019/01/06/reading_list_2018</id>
   
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://www.facebook.com/notes/alex-nisnevich/reading-list-2018/10155545261246685/&quot;&gt;&lt;i&gt;[Originally posted on Facebook]&lt;/i&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s what I read last year:&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Dictionary of the Khazars: A Lexicon Novel (“female” edition)&lt;/i&gt; – Milorad Pavič (tr. Christina Pribicevic-Zoric)&lt;/div&gt;

&lt;p&gt;This is exactly the kind of fun, eccentric modernist fiction that I love so much: a novel in dictionary form, with the entries telling a richly interlinked narrative spanning three time periods, loosely inspired by the 9th century mass conversion of the Khazar people. Magical realism with an emphasis on the “magical”, it’s all written in a dreamy, exuberantly fantastical voice, as though it were a fairy tale recounted by a really good storyteller.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Patriots&lt;/i&gt; – Sana Krasikov&lt;/div&gt;

&lt;p&gt;A contemporary equivalent to those great Russian epics of the nineteenth century. The Patriots is a sprawling novel, zigzagging back and forth between Russia and America and between past and present, as it tells the tale of one family’s impossibly complex history. The narrative has its share of melodrama, but it doesn’t lose its sense of universality, and I felt that I came out of it with a newfound understanding of (and appreciation for) my own family.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Amusing Ourselves to Death: Public Discourse in the Age of Show Business&lt;/i&gt; – Neil Postman&lt;/div&gt;

&lt;p&gt;Postman’s thesis that our ever-more-sophisticated entertainment industry is drugging society into helplessness doesn’t feel particularly novel, but he argues his point masterfully. Particularly intriguing was his exploration of the importance of rational argument in the Age of Reason (e.g. his comparison of the Lincoln-Douglas debates to political debate today). Postman doesn’t offer much in the way of concrete solutions, but this book is more timely than ever (and his analysis of Reagan as the “TV entertainer president” is eerily prescient…). &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Earthsea (first trilogy)&lt;/i&gt; – Ursula K. Le Guin&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(A Wizard of Earthsea ★★★★, The Tombs of Atuan ★★★½, The Furthest Shore ★★★½)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I can’t believe I waited so long to start reading the &lt;em&gt;Earthsea&lt;/em&gt; books! This is Fantasy at its best: a depiction of a truly unique world (not just Middle-Ages-plus-pointy-ears), used as a backdrop for an exploration of serious themes through a humanist lens, rather than a sequence of mindless violence.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Pale Fire&lt;/i&gt; – Vladimir Nabokov&lt;/div&gt;

&lt;p&gt;A bold experiment in form, &lt;em&gt;Pale Fire&lt;/em&gt; is a novel presented as a collection of lengthy, digressive annotations to a (fictional) epic poem. It’s also perhaps one of Nabokov’s most autobiographical works, in its own way – it’s hard not to see Nabokov himself in Kinbote, his refugee literature professor with a mysterious past. Though it’s odd, then, that Nabokov makes his protagonist so unsympathetic – ultimately, a deranged stalker. Still, the rich, opinionated narrative voice makes it a joy to read.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Great Pianists&lt;/i&gt; – Harold C. Schonberg&lt;/div&gt;

&lt;p&gt;Schonberg attempts a doubly-impossible feat – to (1) compare how the great pianoforte players, from Mozart to Gould, have approached the art of piano playing, despite a lack of clear sources, and (2) to then explain his results in a readable way – and yet not only succeeds, but also in the process presents the lives of these pianists in such an engaging way that I’d say that anyone interested in music would appreciate this book.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;White Noise&lt;/i&gt; – Don DeLillo&lt;/div&gt;

&lt;p&gt;DeLillo satirizes academia by making every character in White Noise, young and old, go through the novel as though they are an indifferent sociologist observing the world around them. The characters – a professor of “Hitler studies” and his family and colleagues – aren’t very relatable figures as a result, but it does make for a hilarious, incisive work, at least until it starts to lag due to the introduction of a more “conventional” (relatively speaking) plot line in the third act. , young and old, go through the novel as though they are an indifferent sociologist observing the world around them. The characters – a professor of “Hitler studies” and his family and colleagues – aren’t very relatable figures as a result, but it does make for a hilarious, incisive work, at least until it starts to lag due to the introduction of a more “conventional” (relatively speaking) plot line in the third act. &lt;em&gt;[thanks Danielle]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;All the Names&lt;/i&gt; – Jose Saramago (tr. Margaret Jull Costa)
&lt;/div&gt;

&lt;p&gt;Saramago does what he does best: present the smallest, most insignificant details in a character’s life in such a way that they seem as weighty and important as the actions of a classical hero. At the same time, he describes bureaucratic procedure so eloquently that the bureaucracy in question (a Central Registrar of records for an unnamed city) becomes a central character in its own right. A beautiful little novel hampered only by a clumsy, abrupt ending.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Some Trick: Thirteen Stories&lt;/i&gt; – Helen DeWitt&lt;/div&gt;

&lt;p&gt;No doubt informed by DeWitt’s personal struggles, the stories contained in Some Trick are full of scathing tear-downs of artistic establishments of all sorts, be they publishers, galleries, tabloid journalists, or pompous academics. But the overarching theme is still a hopeful one: despite all the struggles and sacrifices, art will still go on, if only because artists simply can’t stop doing art. Highlights: “On the Town”, “The French Style of Mlle Matsumoto”.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Snail on the Slope&lt;/i&gt; – Arkady &amp;amp; Boris Strugatsky (tr. Olena Bormashenko)&lt;/div&gt;

&lt;p&gt;Not my first time trying to read &lt;em&gt;The Snail on the Slope&lt;/em&gt;, but the first time that I felt like I “got” it, no doubt thanks to Bormashenko’s excellent new translation. Boris later described this novel as a meditation on the conflict between humanity’s past, present, and future. It may be that, but it is also an excellent Kafkaesque tale alternating between two protagonists: one desperately trying to enter an enigmatic forest despite the objections of a faceless bureaucracy, and the other trying just as desperately to escape the forest.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Gravity's Rainbow&lt;/i&gt; – Thomas Pynchon&lt;/div&gt;

&lt;p&gt;I’m torn. i want to hate Gravity’s Rainbow because of the months of arduous reading that it took to get to the end of it – but how could I hate this book? It’s by turns hilarious and tragic, and so dense with allusions to everything under the sun that I feel like I understood half of it, at best, even so, the half that I did understand made it worthwhile. Despite its absurdity, it’s perhaps one of the most realistic fictional depictions of the military-industrial complex and the horrors of war. But I’m not sure. I’m still not done processing what I’ve read. &lt;em&gt;[thanks dad]&lt;/em&gt;&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Seven Days in the Art World&lt;/i&gt; – Sarah Thornton&lt;/div&gt;

&lt;p&gt;If you still have any romantic preconceptions about the role of an artist today, this is the book to dispel them. Thornton masterfully weaves together interviews and observations of all the different interlocking circles underpinning the art world, from collectors to critics, to, finally, and almost as an afterthought, the artists themselves. I certainly can’t look at contemporary art the same way anymore. &lt;em&gt;[thanks Emily]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Leviathan Wakes&lt;/i&gt; – James S. A. Corey&lt;/div&gt;

&lt;p&gt;Fun, fast-paced space noir in a richly crafted near-future universe with a cynical vibe. I couldn’t stop turning pages while reading it, but nonetheless I don’t feel a strong urge to read the sequels. &lt;em&gt;[thanks Jacob]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;In Search of the Hunters and their Tribes: Studies in the History and Culture of the Taiwanese Indigenous People&lt;/i&gt; – ed. David Faure&lt;/div&gt;

&lt;p&gt;There isn’t much English-language books out there on Taiwanese indigenous people, but fortunately this is a good one: a fascinating, meticulously-researched collection of essays on the topic of identity among indigenous tribes in the past and present. I was especially struck by Kai Yiu Chan’s essay tracing the history of plastic bead manufacturing and its effects on the social structure of the Paiwan and Rukai tribes.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Displaced: Refugee Writers on Refugee Lives&lt;/i&gt; – ed. Viet Thanh Nguyen&lt;/div&gt;

&lt;p&gt;A timely collection of powerful accounts of refugee experiences around the world, told from a wide variety of different perspectives, with tone ranging from defiant to heartbreaking. Highlights: “Guests of the Holy Roman Empress Maria Theresa” by Lev Golinkin, “God’s Fate” by Aleksandar Hemon, “The Ungrateful Refugee” by Dina Nayeri. &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Piano Trio: Its History, Technique, and Repertoire&lt;/i&gt; – Basil Smallman&lt;/div&gt;

&lt;p&gt;A comprehensive, if dry, account of the history and theory of my favorite chamber music form.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Blocked: Stories from the World of Online Dating&lt;/i&gt; – ed. Ally Schwed&lt;/div&gt;

&lt;p&gt;At their best, these short comics provide a unique, intimate look at the realities of modern dating. But too many of them just devolve to cliché.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Denial of Death&lt;/i&gt; – Ernest Becker&lt;/div&gt;

&lt;p&gt;I had high hopes for Becker’s &lt;em&gt;magnum opus&lt;/em&gt; tracing most human behavior to our innate fear of death. And it did start off strong with its exploration of the “immortality project” concept in the first few chapters (albeit working with an outdated view of mental illness). Then, for seemingly no reason, came some bizarre chapters attempting to psychoanalyze Freud himself, and the whole thing ended on a religious note that seemed rather a cop-out.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Soloist&lt;/i&gt; – Mark Salzman&lt;/div&gt;

&lt;p&gt;A pretty good character study of an aging musical ex-prodigy, inexplicably crossed with a ridiculous courtroom drama fearing embarrassingly dated portrayals of Zen Buddhism and a cringeworthy romance subplot.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Very Special Relativity: An Illustrated Guide&lt;/i&gt; – Sander Bais&lt;/div&gt;

&lt;p&gt;An interesting attempt to explain special relativity entirely using geometric reasoning with spacetime diagrams, but it didn’t really work for me. Around halfway through it all started to fall apart and made me wish that I could see some equations for a change, rather than the fiendishly complicated diagrams that ensued. &lt;em&gt;[thanks Greg]&lt;/em&gt;&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Roadside Picnic Revisited&lt;/i&gt; – Michael Andre-Druissi&lt;/div&gt;

&lt;p&gt;The author spends six essays saying absolutely nothing of substance. Good English-language analysis of the Strugatsky brothers’ literature does exist (see, e.g. Istvan Csicsery-Ronay’s essays), but this ain’t it.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Creating Land-Use Maps with an HP 7475A Plotter</title>
   
    <link href="http://alex.nisnevich.com/blog/2018/09/15/plotting_land_use_maps.html"/>
   
   <updated>2018-09-15T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2018/09/15/plotting_land_use_maps</id>
   
    <content type="html">&lt;h2 class=&quot;subtitle&quot;&gt;(Or, the week I finally went off the hipster deep end)&lt;/h2&gt;

&lt;p&gt;About six months ago, I read Tobias Toft’s excellent article &lt;a href=&quot;http://www.tobiastoft.com/posts/an-intro-to-pen-plotters&quot;&gt;“An intro to Pen Plotters”&lt;/a&gt;. Inspired by the wonderful art he demonstrated, I set out to get my own plotter - a drawing robot from the pre-printer age - to play with.&lt;/p&gt;

&lt;p&gt;This is the story of my first experiment with pen plotters – plotting a land-use map of Downtown Berkeley:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;/blog/images/plotting_maps_5.png&quot; style=&quot;width: 95%&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;setting-up-and-using-an-hp-7475a-plotter&quot;&gt;Setting up and using an HP 7475A plotter&lt;/h3&gt;

&lt;p&gt;I took Tobias’s advice and took to eBay to look for HP 7xxx-series plotters. I found a cheap HP 7475A that looked to be in good condition, and eagerly awaited its arrival … only to discover that I hadn’t been careful enough in looking at the listing and that the plotter came with the dreaded &lt;a href=&quot;https://en.wikipedia.org/wiki/IEEE-488&quot;&gt;HP-IB&lt;/a&gt; port:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;https://i.imgur.com/htvS654.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I didn’t know this at the time, but it turns out the that HP 7475A was one of the few HP pen plotters that came in two models: one with a forward-compatible serial port and one with a backward-compatible HP-IB port. Not having any idea what to do with this strange plug (it’s not 1985 anymore, after all), I turned to the internet and discovered &lt;a href=&quot;https://softsolder.com/2015/04/20/hp-7475a-plotter-rehabilitation/&quot;&gt;a post&lt;/a&gt; that began a sentence with “&lt;em&gt;FWIW, if you have an HP-IB plotter, you should probably just hack an Arduino into the motor control connections…&lt;/em&gt;” Yikes, that’s a bit more than I’d signed up for.&lt;/p&gt;

&lt;p&gt;Fortunately, the eBay seller was understanding and let me return the HP-IB plotter. Eventually I managed to find an HP 7475A with a serial port. From there, I was able to follow Tobias’s instructions. I got a &lt;a href=&quot;https://www.amazon.com/gp/product/B00066HL50&quot;&gt;null modem cable&lt;/a&gt; and a &lt;a href=&quot;https://www.amazon.com/gp/product/B00IDSM6BW&quot;&gt;serial-to-USB converter cable&lt;/a&gt; and plugged the plotter into my Macbook.&lt;/p&gt;

&lt;p&gt;I tested the plotter out by sending some commands over &lt;a href=&quot;http://freeware.the-meiers.org/&quot;&gt;Coolterm&lt;/a&gt;, but found it pretty finicky. After trying a few different terminal emulators and plotting utilities, I discovered the excellent &lt;a href=&quot;http://sites.music.columbia.edu/cmc/chiplotle/&quot;&gt;Chiplotle&lt;/a&gt; library, and have been using it ever since. For interactive plotting I load the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chiplotle&lt;/code&gt; shell, and for quickly plotting HPGL files created separately I use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plot_hpgl_file.py&lt;/code&gt; script.&lt;/p&gt;

&lt;h3 id=&quot;loading-and-exporting-shapefiles&quot;&gt;Loading and Exporting Shapefiles&lt;/h3&gt;

&lt;p&gt;The first step to plotting a map is loading the appropriate shapefile in a GIS environment (I use the open-source &lt;a href=&quot;https://qgis.org/en/site/&quot;&gt;QGIS&lt;/a&gt;) and exporting each desired layer as PDF. I followed the instructions in the &lt;a href=&quot;https://docs.qgis.org/2.18/en/docs/training_manual/foreword/preparing_data.html#hard-ty&quot;&gt;“Preparing Data”&lt;/a&gt; section of the QGIS tutorial to load a map of Berkeley from &lt;a href=&quot;http://www.openstreetmap.org/&quot;&gt;OpenStreetMap&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;/blog/images/plotting_maps_1.png&quot; style=&quot;width: 100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;From there, I exported a PDF for each layer I was interested in:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;roads.pdf&lt;/code&gt; is the set of lines (roads, train tracks, paths, etc) with labels removed&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;comm.pdf&lt;/code&gt; is the union of the “commercial” and “retail” polygon layers&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inst.pdf&lt;/code&gt; is the “institutional” polygon layer (admittedly this layer doesn’t have a lot in it)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;green.pdf&lt;/code&gt; is the union of the “farmland”, “forest”, “grass”, and “recreational_ground” polygon layers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Initially I separately exported the “residential” layer as well, but I found that it cluttered up the map too much. There is also a layer consisting of all structures, which likewise would have been cool to use (in particular, it would have made the university grounds on the right side look less sparse) but likewise it caused too much clutter.&lt;/p&gt;

&lt;p&gt;I selected each layer (or set of layers) in turn and exported to PDF (&lt;strong&gt;Project | Export to PDF&lt;/strong&gt;), being careful to preserve the exact same dimensions and map coordinates between each PDF.&lt;/p&gt;

&lt;h3 id=&quot;converting-pdf---hpgl&quot;&gt;Converting PDF -&amp;gt; HPGL&lt;/h3&gt;

&lt;p&gt;Now that I had a bunch of PDFs, I needed to convert them to the HPGL format for plotting. I couldn’t find any way to convert PDF -&amp;gt; HPGL in one operation, and settled for converting PDF -&amp;gt; PS using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pdf2ps&lt;/code&gt; and PS -&amp;gt; HGPL using &lt;a href=&quot;http://www.pstoedit.net/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pstoedit&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I wrote a simple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pdf2hpgl.sh&lt;/code&gt; script that, given a list of pairs of PDF file paths and pen numbers (for example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pdf2hpgl.sh roads.pdf 3 comm.pdf 5 inst.pdf 4 green.ps 2&lt;/code&gt;), converts each PDF file into an HPGL file, changes all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SP&lt;/code&gt; (select pen) commands to use the given pen number (instead of the more-or-less random pen selections made by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pstoedit&lt;/code&gt;), and concatenates the result together into one big HPGL file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Requires pdf2ps and pstoedit.&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;OUTPUT_FILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;out.hpgl&quot;&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$OUTPUT_FILE&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;((&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;$#&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; 2 &lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PNG_FILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;PEN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$2&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;shift &lt;/span&gt;2

  &lt;span class=&quot;nv&quot;&gt;PS_FILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PNG_FILE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%.*&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.ps&quot;&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;HPGL_FILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PNG_FILE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%.*&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.hpgl&quot;&lt;/span&gt;

  pdf2ps &lt;span class=&quot;nv&quot;&gt;$PNG_FILE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$PS_FILE&lt;/span&gt;
  pstoedit &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; plot-hpgl &lt;span class=&quot;nv&quot;&gt;$PS_FILE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$HPGL_FILE&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$HPGL_FILE&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;s/SP[0-9]*;/SP&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PEN&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;;/g&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$OUTPUT_FILE&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$PS_FILE&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$HPGL_FILE&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And &lt;em&gt;voila&lt;/em&gt;, we now have an HPGL file that we can send directly to the plotter.&lt;/p&gt;

&lt;p&gt;Here’s what it looks like with just the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;roads&lt;/code&gt; layer plotted:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;/blog/images/plotting_maps_2.jpg&quot; style=&quot;width: 95%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, the polygon layers don’t end up looking very good when simply overlaid on top of the roads layer &lt;em&gt;(I neglected to take a photo of this; you’ll have to take my word for it)&lt;/em&gt;, since they’re just rectangular outlines that are difficult to see next to the black lines of the roads.&lt;/p&gt;

&lt;p&gt;If we want to display the land-use layers in a legible way, we’ll need to get a little creative. How about turning those polygons into hatching lines?&lt;/p&gt;

&lt;h3 id=&quot;creating-hatching-with-adobe-illustrator&quot;&gt;Creating Hatching with Adobe Illustrator&lt;/h3&gt;

&lt;p&gt;I decided to use Adobe Illustrator to generate the hatching lines, but me not being an Illustrator expert by any means, I spent many hours fruitlessly trying different tools to no avail. The issue was that I needed to create a PS file with just the lines themselves – no hatching patterns defined, no masking layers – because the HPGL dialect spoken by the HP 7475A (as opposed to the later, more sophisticated HP-GL/2) doesn’t support any of these fancy features.&lt;/p&gt;

&lt;p&gt;I finally gave up on figuring this out on my own, and asked Stack Exchange, where I got &lt;a href=&quot;https://graphicdesign.stackexchange.com/a/109030/120870&quot;&gt;my answer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The workflow I finally settled on is a bit convoluted, but it does work:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Load the PS file corresponding to a single layer in Illustrator (using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pdf2ps&lt;/code&gt; to generate the PS from the QGIS PDF export).&lt;/li&gt;
  &lt;li&gt;Remove the background layer to leave just the layer with the polygon paths.&lt;/li&gt;
  &lt;li&gt;Select the remaining layer. If there are multiple paths, use &lt;strong&gt;Pathfinder | Unite&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;With the layer still selected, create a compound path with &lt;strong&gt;Object | Compound Path | Make&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Fill the path with &lt;a href=&quot;https://github.com/AlexNisnevich/hp7475a/blob/master/Hatching.ai&quot;&gt;a pattern I made of diagonal strokes&lt;/a&gt;, and remove the stroke outline.&lt;/li&gt;
  &lt;li&gt;Adjust the spacing of the hatching lines as desired with &lt;strong&gt;Object | Transform | Scale&lt;/strong&gt; (with only “Transform Patterns” checked).&lt;/li&gt;
  &lt;li&gt;Convert the compount path to a group of hatching lines with &lt;strong&gt;Object | Expand&lt;/strong&gt; and then &lt;strong&gt;Object | Path | Outline Stroke&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Release the clipping mask: &lt;strong&gt;Object | Clipping Mask | Release&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Select everything and click &lt;strong&gt;Pathfinder | Crop&lt;/strong&gt; in the &lt;strong&gt;Properties&lt;/strong&gt; panel.&lt;/li&gt;
  &lt;li&gt;Finally, export the result back to PS by printing to Postscript using the “Device Independent” PPD. I then converted the PS file to HPGL using a modified version of my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pdf2hpgl.sh&lt;/code&gt; script that simply skips the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pdf2ps&lt;/code&gt; step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s what it looks like when I plot the resulting hatch lines for just one layer (the “commercial” layer):&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;/blog/images/plotting_maps_3.jpg&quot; style=&quot;width: 95%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Not bad, huh?&lt;/p&gt;

&lt;p&gt;Once we have hatching for one layer working, we can do the rest of them just by repeating the process for each layer. I did have to carefully align each layer together in Illustrator (and then print them out to Postscript one at a time by selectively hiding layers):&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;/blog/images/plotting_maps_4.png&quot; style=&quot;width: 100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That said, you can create some cool glitchy art by neglecting to properly align the layers:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;/blog/images/plotting_maps_6.jpg&quot; style=&quot;width: 80%&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;the-final-product&quot;&gt;The Final Product&lt;/h3&gt;

&lt;p&gt;I ran &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plot_hpgl_file.py&lt;/code&gt; and crossed my fingers. After 10 minutes of meticulous plotting, the map came out of the plotter, and actually looked halfway decent. I added some text and carefully cut the paper from 11”x17” to 11”x14” to reduce the amount of unsightly white space at the bottom:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;/blog/images/plotting_maps_5.png&quot; style=&quot;width: 95%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Not perfect, but it’s certainly not bad for a week’s work.&lt;/p&gt;

&lt;p&gt;I haven’t had the chance to do much else with the pen plotter yet, but hopefully this gave you a taste of what these little robots can do. And who knows, maybe I can inspire someone else to save a plotter from a junkyard, like Tobias Toft’s article inspired me. (Just watch out for the dreaded HP-IB port…)&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Gator Album Release</title>
   
    <link href="http://alex.nisnevich.com/blog/2018/08/19/gator_album_release.html"/>
   
   <updated>2018-08-19T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2018/08/19/gator_album_release</id>
   
    <content type="html">&lt;p&gt;After 2½ years of recording, re-recording, production, mixing, and mastering, my swamp metal band’s album is finally out!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Gator&lt;/em&gt; is an eclectic mix of heavy metal, psychedelic rock, and classic blues. I have a feeling you’ll like it.&lt;/p&gt;

&lt;p&gt;The album is available on Spotify …&lt;/p&gt;

&lt;iframe src=&quot;https://open.spotify.com/embed?uri=spotify:album:5l3T8pQA4LLEnBhWUrQK6b&amp;amp;view=coverart&quot; width=&quot;500&quot; height=&quot;300&quot; frameborder=&quot;0&quot; allowtransparency=&quot;true&quot; allow=&quot;encrypted-media&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;… as well as &lt;a href=&quot;https://gatorcomingforyou.bandcamp.com/album/gator&quot;&gt;Bandcamp&lt;/a&gt; and &lt;a href=&quot;https://soundcloud.com/gatorcomingforyou/sets/gator&quot;&gt;Soundcloud&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you like what you hear, &lt;a href=&quot;https://qrates.com/projects/16558&quot;&gt;pre-order the vinyl&lt;/a&gt;! We’re doing a very limited press of 200 copies.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/gator-covers.png&quot; style=&quot;width: 100%&quot; /&gt;&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Reading List - 2017</title>
   
    <link href="http://alex.nisnevich.com/blog/2018/01/11/reading_list_2017.html"/>
   
   <updated>2018-01-11T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2018/01/11/reading_list_2017</id>
   
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://www.facebook.com/notes/alex-nisnevich/reading-list-2017/10154826038166685&quot;&gt;&lt;i&gt;[Originally posted on Facebook]&lt;/i&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Per tradition, here are all the books I read last year.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Unfathomable City: A New Orleans Atlas&lt;/i&gt; – Rebecca Solnit &amp;amp; Rebecca Snedeker&lt;/div&gt;

&lt;p&gt;★★★★½. While &lt;em&gt;Infinite City&lt;/em&gt; revealed fresh secrets of a city I already knew, &lt;em&gt;Unfathomable City&lt;/em&gt; served as an unorthodox travel guide. Reading it on the plane to New Orleans, I was struck by the deep sense of love for the city that I could feel in each of the essays and maps, and the bustling energy evoked by the book was contagious.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Ghostwritten&lt;/i&gt; – David Mitchell&lt;/div&gt;

&lt;p&gt;★★★★. My first David Mitchell novel, and hopefully not my last. I loved some of the interlocking segments (particularly “Holy Mountain” and “Cape Clear Island”), didn’t care for a few of them (especially “London”), and had mixed feelings about the spectacular ending. &lt;em&gt;[thanks Linchuan]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;A People's History of the United States&lt;/i&gt; – Howard Zinn&lt;/div&gt;

&lt;p&gt;★★★★½. It’s become almost a meme in certain circles, but this is nonetheless a remarkable book. It’s not just a different perspective on history, but a different way of looking at history altogether: as the actions of ordinary people fighting for better lives rather than the whims of a few “great men”. Tolstoy would be proud. &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Speaking American: How Y’all, Youse, and You Guys Talk&lt;/i&gt; – Josh Katz&lt;/div&gt;

&lt;p&gt;★★★. A neat visualization of a massive study. I just wish that there was some analysis to accompany it. &lt;em&gt;[thanks mom &amp;amp; dad]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The ABCs of Socialism&lt;/i&gt; – ed. Bhaskar Sunkara&lt;/div&gt;

&lt;p&gt;★★★★. Jacobin’s first book-length publication is a slim volume that rebuts common misconceptions about socialism in a clear, no-nonsense prose that’s anything but preachy. Its design is fantastic as well, from Wriggleworth’s cartoon illustrations to the simple question-and-answer bookends for each chapter.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Absolutely on Music&lt;/i&gt; – Seiji Ozawa &amp;amp; Haruki Murakami (tr. Jay Rubin)&lt;/div&gt;

&lt;p&gt;★★★★ Murakami, an avid classical music fan with no musical background, is the perfect foil to Ozawa, the knowledgeable yet affable conductor. Reading their casual chats about Beethoven and Mahler is great fun.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;BART: A Dramatic History&lt;/i&gt; – Michael C. Healy&lt;/div&gt;

&lt;p&gt;★★★★. An engaging account of BART’s history and operations, full of amusing anecdotes and overly excited chapter titles like “1977 and 1978 See Several Improvements and Added Services”. Healy was BART’s chief spokesperson for 32 years, and, while his insider status introduces a lot of bias, it also allows him to pepper the book with behind-the-scenes details.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Shape of Things&lt;/i&gt; – Neil LaBute&lt;/div&gt;

&lt;p&gt;★★★. An interesting conceit, it was difficult to care about such incredibly superficial characters. &lt;em&gt;[thanks Asali and JP]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The City Shaped: Urban Patterns &amp;amp; Meanings Through History&lt;/i&gt; – Spiro Kostof&lt;/div&gt;

&lt;p&gt;★★★★½. Kostof argues that traditional histories of urban design make the fatal flaw of assuming a linear progression of styles, and presents an alternative history based on morphology. In five chapters (“Organic” Patterns; The Grid; City as Diagram; The Grand Manner; The Urban Skyline), Kostof describes the development of five approaches to city-building, their patterns and variations, and the meanings ascribed to them throughout history. Through it all, he writes with such an engaging voice that you feel like you’re getting a tour of the world’s cities rather than reading a history book. I can’t look at a city the same way after reading it.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Snakes and Ladders: Glimpses of Modern India&lt;/i&gt; – Gita Mehta&lt;/div&gt;

&lt;p&gt;★★★½. A chaotic and exciting book about a chaotic and exciting country. In her essays, Mehta gives a whirlwind tour of India throughout the 20th century, covering politics, economics, spirituality, and culture, but the book really shines when she recounts her stories of growing up with independence-activist parents in the 1940s. &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;How to Be a Jewish Mother&lt;/i&gt; – Dan Greenberg&lt;/div&gt;

&lt;p&gt;Cute but dated vintage humor book.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Visual Display of Quantitative Information&lt;/i&gt; – Edward Tufte&lt;/div&gt;

&lt;p&gt;★★★★. The book is visually gorgeous, and Tufte’s basic laws of information design (in particular the “data-ink ratio”) provide a solid framework for reasoning about information graphics. But I didn’t learn as much as I could have – the book was short and devoted too much time to some pet techniques that didn’t seem particularly useful nowadays.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Secondhand Time: The Last of the Soviets&lt;/i&gt; – Svetlana Alexievich (tr.Bela Shayevich)&lt;/div&gt;

&lt;p&gt;★★★★★. If you’re going to read any book about contemporary Russia, make it this one. Alexievich writes what she calls “novels in voices”: stories told entirely through edited transcripts of hundreds of oral interviews. She’s written about the Afghan war and Chernobyl, but Secondhand Time is arguably her most ambitious piece: a sprawling account of the first two decades of post-Soviet Russia and the disillusionment Russians felt after the collapse of the Soviet Union. She interviews everyone from gulag prisoners to NKVD interrogators, from jet-setting business-people to Central Asian migrant workers. It’ll make you laugh, it’ll make you cry, it’ll make you feel a profound sense of empathy for a country in transition.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The New Urbanism: Toward an Architecture of Community&lt;/i&gt; – Peter Katz&lt;/div&gt;

&lt;p&gt;★★★½. There are some nice essays by the New Urbanism pioneers (Calthorpe, DPZ, et al), but what really makes this book is the case studies of New Urbanism communities of all shapes throughout the U.S., from new towns like Seaside to revitalization projects like downtown Providence, all complete with beautiful city plans and architectural examples. A great source of inspiration.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Lincoln in the Bardo&lt;/i&gt; – George Saunders&lt;/div&gt;

&lt;p&gt;★★★★. Imagine that a novella, a play, and a nonfiction biography had a bastard child. The action takes place in a cemetery, and, though it’s ostensibly about Abraham Lincoln, he only appears for a few pages. It’s difficult to describe what Lincoln in the Bardo &lt;em&gt;is&lt;/em&gt; exactly, but whatever it is, it’s gripping, poignant, and endlessly inventive.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Hope in the Dark&lt;/i&gt; – Rebecca Solnit&lt;/div&gt;

&lt;p&gt;★★★★. In this collection of essays, Solnit argues that hope is just as important as fear in driving activism. Though most of the book dates back to 2004, it’s as important a lesson now as ever.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The God of Small Things&lt;/i&gt; – Arundhati Roy&lt;/div&gt;

&lt;p&gt;★★★★★. The most beautiful and heartbreaking novel I read this year. Roy’s descriptions of life in rural Kerala are so vivid that you can practically smell the red banana trees. The story is intricately plotted, jumping backward and forward in time as the tale of the Kochamma family unfolds, and it keeps you hoping against hope even as a mounting sense of dread keeps building into an horrific climax. It’s that rare sort of novel that not only sticks with you, but makes you feel like somehow it’s been a part of you all along. &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Works: Anatomy of a City&lt;/i&gt; – Kate Ascher&lt;/div&gt;

&lt;p&gt;★★★½. An enjoyable overview of the transportation, energy, communications, and waste infrastructure of New York City filled with marvelous diagrams. No profound insights here, but it’s a pleasure to leaf through and chock-full of surprising facts. &lt;em&gt;[thanks Lily]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Kasimir Malevich and Suprematism&lt;/i&gt; – Gilles Néret&lt;/div&gt;

&lt;p&gt;★★★½. An engaging and meticulously researched overview of an under-appreciated modern artist. Malevich is a somewhat enigmatic figure, but Néret does an admirable job of explaining his philosophy. &lt;em&gt;[thanks mom]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Where'd You Go, Bernadette&lt;/i&gt; – Maria Semple&lt;/div&gt;

&lt;p&gt;★★★★. A damn good satire of the tech industry, suburban life, the art world, and everything in between. Semple hooks you in with her goofy humor and it’s not until you’re halfway in that you suddenly realize how much you’ve cared about all these flawed yet lovable characters all along. I couldn’t put it down, even through some questionable plot developments near the end. &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Yellow Shadow&lt;/i&gt; – V M Steele&lt;/div&gt;

&lt;p&gt;★½. The casual racism at the core of the story doesn’t age well at all, and the whole plot is frankly ridiculous.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Nonstop Metropolis: A New York City Atlas&lt;/i&gt; – Rebecca Solnit &amp;amp; Joshua Jelly-Shapiro&lt;/div&gt;

&lt;p&gt;★★★★. A lot of good pieces (I particularly liked the ones on the evolution of hip-hop), but overall it was more repetitive and less cohesive than its predecessors. The start was particularly shaky, with the first 5-6 maps all tackling similar ideas.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Best Russian Short Stories&lt;/i&gt; – Thomas Seltzer, ed.&lt;/div&gt;

&lt;p&gt;★★★★. A comprehensive and surprisingly well-curated collection of Russian short stories from the 1920s, featuring both the heavy-hitters like Tolstoy and Gorky and relative unknowns like Saltykov-Shchedrin and Sologub, all in lively, readable translations. Highlights: “The Queen of Spades” (Pushkin), “The Overcoat” (Gogol), “The Bet” (Chekhov), “Lazarus” (Andreyev). &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Baudolino&lt;/i&gt; – Umberto Eco (tr. William Weaver)&lt;/div&gt;

&lt;p&gt;★★★★. A lighthearted medieval adventure tale told in the style of medieval adventure tales, and an exploration of languages (much of the novel is written in a garbled pseudo-Latin), the complicated role of religion in medieval politics, and how objects obtain meaning (much of the adventure involves religious relics, real and fictional). It’s not hard to see why I enjoyed reading it so much. &lt;em&gt;[thanks, Bakery Bar in NOLA]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Pastoralia&lt;/i&gt; – George Saunders&lt;/div&gt;

&lt;p&gt;★★★★. Six black comedy stories from the master. It seems that Saunders was still finding his voice in these stories: he doesn’t demonstrate quite the linguistic sureness that he has in Tenth of December and a couple of these stories didn’t speak to me at all. But it’s still Saunders, and “Pastoralia” and “Sea Oak” rank among his best. &lt;em&gt;[thanks Danielle]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Capital in the Twenty-First Century&lt;/i&gt; – Thomas Piketty (tr. Arthur Goldhammer)&lt;/div&gt;

&lt;p&gt;★★★★½. Finally, an approach to economics that interests me. Piketty throws out the conventional methodology of working up from abstracted models and instead employs a data-driven technique informed largely by historical records. In 12 chart-heavy chapters, he traces the development of the income-capital ratio and the distribution of income and capital throughout the world (primarily France and the U.S.), and argues that the economy trends naturally towards greater wealth concentration. These chapters diagnosing the state of capital are engaging and have given me a great deal to think about, but his final chapters suggesting potential solutions felt rhetorically weak, seeming almost like an afterthought. Piketty struck me as a far better economist than politician.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Watcher and Other Stories&lt;/i&gt; – Italo Calvino (tr. Weaver &amp;amp; Colquhoun)&lt;/div&gt;

&lt;p&gt;★★★. “The Argentine Ant” is a great little horror tale, but I didn’t care much for the other two stories. As far as early Calvino stories go, I much preferred the ones collected in &lt;em&gt;Difficult Loves&lt;/em&gt;.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>The Leningrad Rock Scene, in Five Albums</title>
   
    <link href="http://alex.nisnevich.com/blog/2017/09/14/leningrad_rock_scene.html"/>
   
   <updated>2017-09-14T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2017/09/14/leningrad_rock_scene</id>
   
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://www.facebook.com/notes/10157717502336828/&quot;&gt;&lt;i&gt;[Originally posted on Facebook]&lt;/i&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://open.spotify.com/playlist/5akD9pxmrnSg9Y0LYXDr0f&quot;&gt;[Listen on Spotify (full albums) – 3 hr 45 min]&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://open.spotify.com/playlist/0eaAEYPsOeXkPTEjELh3od&quot;&gt;[Listen on Spotify (just the highlights) – 1 hr 22 min]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Though largely unknown in the West, Leningrad in the 1980s was a center for Soviet underground rock music.&lt;/p&gt;

&lt;p&gt;Rock music existed in the Soviet Union ever since the first Western bootleg tapes were smuggled across the border, but two pivotal events in the early 1980s enabled the Leningrad scene to break out: the 1980 Tbilisi Rock Festival, in which the Leningrad band Aquarium’s outlandish stage antics turned them into Soviet counter-cultural celebrities overnight; and the 1981 opening of the Leningrad Rock Club, under the watchful patronage of the KGB.&lt;/p&gt;

&lt;p&gt;Leningrad rock groups existed in a fickle and uneasy peace with the authorities. They could attract huge crowds at the Leningrad Rock Club, but their lyrics were strictly controlled. In 1984, Chernenko ordered a crackdown on rock music, accusing musicians of “moral sabotage”, while the next year, Gorbachev’s perestroika offered rock musicians unprecedented freedom. Even then, freedom came with a price, as bands coming out of the underground were accused by diehard fans of selling out.&lt;/p&gt;

&lt;p&gt;As a result, Leningrad rock musicians had to grapple with a whole host of contradictions. How could they stay true to Russia’s musical traditions while absorbing the influence of the Western rock music so easily accessible across the Finnish border? How could they write lyrics that reflected the feelings of their contemporaries without catching the attention of the authorities? And how could they get the benefits of official music-promotion channels without earning the ire of longtime fans?&lt;/p&gt;

&lt;p&gt;Here are five of my favorite albums (in chronological order) by Leningrad rock bands, highlighting the breadth of the scene, from folk to punk to prog. Each of these bands came up with their own solution to these contradictions, and established a unique style in the process.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img style=&quot;float: right; max-width: 300px&quot; src=&quot;https://e.snmc.io/i/600/w/88fc2be4599abd031dd210eca9555c26/5444484/%D0%BA%D0%B8%D0%BD%D0%BE-kino-45-Cover-Art.jpg&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;45-1982&quot;&gt;&lt;em&gt;45&lt;/em&gt; (1982)&lt;/h3&gt;
&lt;h4 id=&quot;by-кино-kino--film&quot;&gt;by Кино [&lt;em&gt;Kino&lt;/em&gt; / Film]&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Listen: &lt;a href=&quot;https://open.spotify.com/album/2QnAu8rEb9K2c2cBU2QBJo&quot;&gt;[Spotify]&lt;/a&gt; &lt;a href=&quot;https://music.apple.com/us/album/45/875239029&quot;&gt;[iTunes]&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=QWWyI7eYqpI&quot;&gt;[Youtube]&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://rateyourmusic.com/release/album/%D0%BA%D0%B8%D0%BD%D0%BE/45/&quot;&gt;RYM&lt;/a&gt; tags: Contemporary Folk / Post-Punk / Psychedelic Folk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Album:&lt;/strong&gt; Kino would go on to become the iconic Soviet band of the 80s, with their frontman, Victor Tsoi, becoming a cult hero. But you could hardly tell from listening to their unassuming debut album, featuring just Tsoi and guitarist Rybin, augmented by several musicians from Aquarium and a drum machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Sound:&lt;/strong&gt; Tsoi’s goal was a “synthetic” rock sound, which Kino would end up perfecting on their later albums. But due to lack of personnel, 45 instead ended up with a heavy folk sound,  with sparse guitars and vocals reminiscent of the countercultural Russian folk artists known as the bards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Lyrics:&lt;/strong&gt; Earnest depictions of the lives of urban youth (“Lots of time, but no more money and no one wants to let me in”), that occasionally veers into metaphor (“I’m planting aluminum cucumbers / On a field of canvas”). &lt;a href=&quot;https://web.archive.org/web/20161129135108/http://russmus.net/band/78/#album-569&quot;&gt;[English translations here.]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Highlights:&lt;/strong&gt; &lt;strong&gt;[1]&lt;/strong&gt; “Время есть, а денег нет” (“Lots of Time, But No Money”), &lt;strong&gt;[2]&lt;/strong&gt; “Просто хочешь ты знать” (“You Just Want to Know”), &lt;strong&gt;[6]&lt;/strong&gt; “Бездельник 2” (“Layabout #2”), &lt;strong&gt;[12]&lt;/strong&gt; “Когда-то ты был битником” (“You Were Once a Beatnik”)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See also:&lt;/strong&gt; Aquarium’s 1982 &lt;a href=&quot;https://rateyourmusic.com/release/album/%D0%B0%D0%BA%D0%B2%D0%B0%D1%80%D0%B8%D1%83%D0%BC/%D0%B0%D0%BA%D1%83%D1%81%D1%82%D0%B8%D0%BA%D0%B0/&quot;&gt;Акустика&lt;/a&gt; (&lt;em&gt;Acoustics&lt;/em&gt;), another folk-inspired rock album from the same year, featuring many of the same musicians in a vastly different context. &lt;a href=&quot;https://open.spotify.com/album/6a00hWxPayklXkfSzqY2Pe&quot;&gt;[Spotify]&lt;/a&gt; &lt;a href=&quot;https://music.apple.com/ru/album/%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D1%8F-%D0%B0%D0%BA%D0%B2%D0%B0%D1%80%D0%B8%D1%83%D0%BC%D0%B0-%D1%82%D0%BE%D0%BC-1-%D0%B0%D0%BA%D1%83%D1%81%D1%82%D0%B8%D0%BA%D0%B0/929047970&quot;&gt;[iTunes]&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=utdI-TyfsGg&amp;amp;list=PL4_6YfB7Jqq632djMW-Pe6JrQY3Hxhglp&quot;&gt;[Youtube]&lt;/a&gt; &lt;a href=&quot;https://web.archive.org/web/20160824040410/http://dharmafish.org/albums/5&quot;&gt;[English lyrics]&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img style=&quot;float: right; max-width: 300px&quot; src=&quot;https://e.snmc.io/i/600/w/e423ac4b692493d2c918b3e54a366625/4033253/%D0%B0%D0%BA%D0%B2%D0%B0%D1%80%D0%B8%D1%83%D0%BC-aquarium-%D1%80%D0%B0%D0%B4%D0%B8%D0%BE-%D0%B0%D1%84%D1%80%D0%B8%D0%BA%D0%B0-radio-afrika-Cover-Art.jpg&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;радио-африка-radio-africa-1983&quot;&gt;&lt;em&gt;Радио Африка&lt;/em&gt; [&lt;em&gt;Radio Africa&lt;/em&gt;] (1983)&lt;/h3&gt;
&lt;h4 id=&quot;by-аквариум-aquarium&quot;&gt;by Аквариум [&lt;em&gt;Aquarium&lt;/em&gt;]&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Listen: &lt;a href=&quot;https://open.spotify.com/album/2EmV0ZjY5oMOCPSlvLXBY9&quot;&gt;[Spotify]&lt;/a&gt; &lt;a href=&quot;https://music.apple.com/ca/album/%D1%80%D0%B0%D0%B4%D0%B8%D0%BE-%D0%B0%D1%84%D1%80%D0%B8%D0%BA%D0%B0/926837780&quot;&gt;iTunes&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=f_pL5ejkiMY&quot;&gt;Youtube&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://rateyourmusic.com/release/album/%D0%B0%D0%BA%D0%B2%D0%B0%D1%80%D0%B8%D1%83%D0%BC/%D1%80%D0%B0%D0%B4%D0%B8%D0%BE-%D0%B0%D1%84%D1%80%D0%B8%D0%BA%D0%B0-radio-afrika/&quot;&gt;RYM&lt;/a&gt; tags: New Wave / Psychedelic Rock / Art Rock / Experimental Rock / Sound Collage / Field Recordings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Album:&lt;/strong&gt; Aquarium bribed the engineers of a Soviet mobile recording studio and enlisted a who’s-who of Leningrad avant-garde musicians to record &lt;em&gt;Radio Africa&lt;/em&gt;, their first concept album. It proved to be such a sensation that the Soviet record label, Melodiya, issued an official, albeit censored, version of it a few years later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Sound:&lt;/strong&gt; Progressive rock meets new wave meets world-music-before-there-was-world-music, in tracks with colorful titles like “Captain Africa” and “Tibetan Tango”, stitched together by the sounds of radio static.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Lyrics:&lt;/strong&gt; All over the place, ranging from sincere ballads to complete nonsense, and frequently a mix of the two (“There are books for the eyes, and books in the form of a pistol / Sit by the window and listen to the noise of great ideas / But if you are young, then you are a fierce opponent of light / This is another plus to the songs of exhausting people”). &lt;a href=&quot;https://web.archive.org/web/20160824040410/http://dharmafish.org/albums/10&quot;&gt;[Some English translations here.]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Highlights:&lt;/strong&gt; &lt;strong&gt;[2]&lt;/strong&gt; “Капитан Африка” (“Captain Africa”), &lt;strong&gt;[3]&lt;/strong&gt; “Песни вычерпывающих людей” (“Songs of Exhausting People”), &lt;strong&gt;[6]&lt;/strong&gt; “Рок-н-ролл мертв” (“Rock’n’Roll is Dead”), &lt;strong&gt;[10]&lt;/strong&gt; “Время Луны” (“Time of the Moon”)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See also:&lt;/strong&gt; Aquarium’s follow-up album, Aquarium’s follow-up album, &lt;a href=&quot;https://rateyourmusic.com/release/album/%D0%B0%D0%BA%D0%B2%D0%B0%D1%80%D0%B8%D1%83%D0%BC/%D0%B4%D0%B5%D0%BD%D1%8C-%D1%81%D0%B5%D1%80%D0%B5%D0%B1%D1%80%D0%B0/&quot;&gt;День серебра&lt;/a&gt; (&lt;em&gt;Silver Day&lt;/em&gt;), which moves away from sound collage and goes into a more old-fashioned psychedelic rock in the vein of Sgt. Pepper. &lt;a href=&quot;https://open.spotify.com/album/6JzW3QgKCjYlEdQL1wxoGu&quot;&gt;[Spotify]&lt;/a&gt; &lt;a href=&quot;https://music.apple.com/us/album/%D0%B4%D0%B5%D0%BD%D1%8C-%D1%81%D0%B5%D1%80%D0%B5%D0%B1%D1%80%D0%B0/926838173&quot;&gt;[iTunes]&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=_SImwsyFsMc&quot;&gt;[Youtube]&lt;/a&gt; &lt;a href=&quot;https://web.archive.org/web/20160824040410/http://dharmafish.org/albums/14&quot;&gt;[English lyrics]&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img style=&quot;float: right; max-width: 300px&quot; src=&quot;https://e.snmc.io/i/600/w/27902a1d279e2f23cb646f7d54a16bab/9690699/nautilus-pompilius-%D0%BF%D0%B5%D1%80%D0%B5%D0%B5%D0%B7%D0%B4-Cover-Art.jpg&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;переезд-pereyezd--move-in-1983&quot;&gt;Переезд [&lt;em&gt;Pereyezd&lt;/em&gt; / Move-in] (1983)&lt;/h3&gt;
&lt;h4 id=&quot;by-nautilus-pompilius&quot;&gt;by Nautilus Pompilius&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Listen: &lt;a href=&quot;https://open.spotify.com/album/1EckcxG7Ap1zf7TyffUTT4&quot;&gt;[Spotify]&lt;/a&gt; &lt;a href=&quot;https://music.apple.com/us/album/%D0%BF%D0%B5%D1%80%D0%B5%D0%B5%D0%B7%D0%B4/1079858054&quot;&gt;[iTunes]&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=tVtLfs6PLQI&quot;&gt;[Youtube]&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://rateyourmusic.com/release/album/nautilus-pompilius/%D0%BF%D0%B5%D1%80%D0%B5%D0%B5%D0%B7%D0%B4/&quot;&gt;RYM&lt;/a&gt; tags: Post-Punk / Art Punk / Hard Rock / Gothic Rock&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Album:&lt;/strong&gt; Several years before they moved to Leningrad, Nautilus Pompilius kicked off the “Ural rock scene” in Sverdlovsk with this album, a collection of various recordings from ‘82 and ‘83.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Sound:&lt;/strong&gt; The first side, recorded in 1983, is mostly lush and melodic, with overlapping guitar lines and passionate falsetto vocals. The second side, recorded in 1982, is noisy and punky, at times reminiscent of the Velvet Underground.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Lyrics:&lt;/strong&gt; Imaginative and conceptual, in the art-rock tradition. There are songs about hawks, about flying ships, about the opera, and even a song entitled “Battle Against the Tycoon”, which is about what you’d expect. &lt;a href=&quot;https://lyricstranslate.com/en/nautilus-pompilius-lyrics.html&quot;&gt;[Some English translations here.]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Highlights:&lt;/strong&gt; &lt;strong&gt;[4]&lt;/strong&gt; “После и снова” (“After and Again”), &lt;strong&gt;[7]&lt;/strong&gt; “Летучий фрегат” (“Flying Frigate”), &lt;strong&gt;[9]&lt;/strong&gt; “Ястребиная свадьба” (“Hawk Wedding”), &lt;strong&gt;[10]&lt;/strong&gt; “Анабасис” (“Anabasis”)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See also:&lt;/strong&gt; Nautilus Pompilius’s 1986 breakout album &lt;a href=&quot;https://rateyourmusic.com/release/album/nautilus-pompilius/%D1%80%D0%B0%D0%B7%D0%BB%D1%83%D0%BA%D0%B0/&quot;&gt;Разлука&lt;/a&gt; (&lt;em&gt;Separation&lt;/em&gt;), which sees them confidently adopt a synthpop aesthetic and features a number of their mid-career hits, such as “Chained Together” and “Alain Delon”. &lt;a href=&quot;https://open.spotify.com/album/1JE6Dm3EneJUYZP6QKXgq9&quot;&gt;[Spotify]&lt;/a&gt; &lt;a href=&quot;https://music.apple.com/us/album/%D1%80%D0%B0%D0%B7%D0%BB%D1%83%D0%BA%D0%B0/1079869414&quot;&gt;[iTunes]&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=QlypehHPxCs&quot;&gt;[Youtube]&lt;/a&gt; &lt;a href=&quot;https://lyricstranslate.com/en/nautilus-pompilius-lyrics.html&quot;&gt;[English lyrics]&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img style=&quot;float: right; max-width: 300px&quot; src=&quot;https://e.snmc.io/i/600/w/b8fbb05618b22ac0272f35629aca5b0f/2576426/%D0%BA%D0%B8%D0%BD%D0%BE-kino-%D0%BD%D0%BE%D1%87%D1%8C-Cover-Art.jpg&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;ночь-noch--night-1986&quot;&gt;Ночь [&lt;em&gt;Noch&lt;/em&gt; / Night] (1986)&lt;/h3&gt;
&lt;h4 id=&quot;by-кино-kino--film-1&quot;&gt;by Кино [&lt;em&gt;Kino&lt;/em&gt; / Film]&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Listen: &lt;a href=&quot;https://open.spotify.com/album/6ghlax8ZNLmprrXB8OAwJB&quot;&gt;[Spotify]&lt;/a&gt; &lt;a href=&quot;https://music.apple.com/us/album/%D0%BD%D0%BE%D1%87%D1%8C/1207567845&quot;&gt;[iTunes]&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=Erww7TMFscU&quot;&gt;[Youtube]&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://rateyourmusic.com/release/album/%D0%BA%D0%B8%D0%BD%D0%BE/%D0%BD%D0%BE%D1%87%D1%8C/&quot;&gt;RYM&lt;/a&gt; tags: Post-Punk / Jangle Pop / New Wave&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Album:&lt;/strong&gt; Noch was the first album of Kino’s “classic” (‘86-’90) lineup, which saw the band completely sever its ties to Aquarium and go off into its own direction. It was this album, along with the band’s appearance in the cult film &lt;a href=&quot;https://en.wikipedia.org/wiki/Assa_(film)&quot;&gt;&lt;em&gt;Assa&lt;/em&gt;&lt;/a&gt; the following year, that placed them into the cultural mainstream.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Sound:&lt;/strong&gt; With Noch, Kino finally achieved the post-punk sound that Tsoi envisioned, with funky bass lines and guitar rhythms by turn menacing and danceable. Think Joy Division, but more upbeat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Lyrics:&lt;/strong&gt; Relatable lyrics about contemporary life, ranging from playful (“We have cigarettes and matches, and a bottle of wine / We saw the night, we walked all night long) to cynical (“You go to the kitchen, But the water here is bitter, / You can’t sleep here, and you don’t want to live here. / Good morning to you, our last hero!”). &lt;a href=&quot;https://web.archive.org/web/20161129135108/http://russmus.net/band/78/#album-573&quot;&gt;[English translations here.]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Highlights:&lt;/strong&gt; &lt;strong&gt;[2]&lt;/strong&gt; “Фильмы” (“Movies”), &lt;strong&gt;[3]&lt;/strong&gt; “Твой номер” (“Your Number”), &lt;strong&gt;[6]&lt;/strong&gt; “Последний герой” (“The Last Hero”), &lt;strong&gt;[11]&lt;/strong&gt; “Мы хотим танцевать” (“We Want to Dance”)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See also:&lt;/strong&gt; Kino’s iconic 1989 album &lt;a href=&quot;https://rateyourmusic.com/release/album/%D0%BA%D0%B8%D0%BD%D0%BE/%D0%B7%D0%B2%D0%B5%D0%B7%D0%B4%D0%B0-%D0%BF%D0%BE-%D0%B8%D0%BC%D0%B5%D0%BD%D0%B8-%D1%81%D0%BE%D0%BB%D0%BD%D1%86%D0%B5/&quot;&gt;Звезда по имени Солнце&lt;/a&gt; (&lt;em&gt;A Star Called the Sun&lt;/em&gt;), a gloomy post-punk masterpiece recorded by the band at the peak of their popularity and shortly before Victor Tsoi’s tragic death. &lt;a href=&quot;https://open.spotify.com/album/6jRDBHMIDlq7IAfkS1nJki&quot;&gt;[Spotify]&lt;/a&gt; &lt;a href=&quot;https://music.apple.com/us/album/%D0%B7%D0%B2%D0%B5%D0%B7%D0%B4%D0%B0-%D0%BF%D0%BE-%D0%B8%D0%BC%D0%B5%D0%BD%D0%B8-%D1%81%D0%BE%D0%BB%D0%BD%D1%86%D0%B5/1207567585&quot;&gt;[iTunes]&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=v13mUXWOpBA&quot;&gt;[Youtube]&lt;/a&gt; &lt;a href=&quot;(https://web.archive.org/web/20161129135108/http://russmus.net/band/78/#album-575)&quot;&gt;[English lyrics]&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;img style=&quot;float: right; max-width: 300px&quot; src=&quot;https://e.snmc.io/i/600/w/4527d8314c037cd66a850026370f0ff7/2062046/%D0%BD%D0%BE%D0%BB%D1%8C-nol-%D0%BF%D0%B5%D1%81%D0%BD%D1%8F-%D0%BE-%D0%B1%D0%B5%D0%B7%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D0%BD%D0%BE%D0%B9-%D0%BB%D1%8E%D0%B1%D0%B2%D0%B8-%D0%BA-%D1%80%D0%BE%D0%B4%D0%B8%D0%BD%D0%B5-Cover-Art.jpg&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;песня-о-безответной-любви-к-родине-song-about-an-unrequited-love-for-the-motherland-1991&quot;&gt;Песня о безответной любви к Родине [&lt;em&gt;Song about an Unrequited Love for the Motherland&lt;/em&gt;] (1991)&lt;/h3&gt;
&lt;h4 id=&quot;by-ноль-nol--zero&quot;&gt;by Ноль [&lt;em&gt;Nol’&lt;/em&gt; / Zero]&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Listen: &lt;a href=&quot;https://open.spotify.com/album/4rf8ZUJbLef53T9W1DVs3w&quot;&gt;[Spotify]&lt;/a&gt; &lt;a href=&quot;https://music.apple.com/us/album/%D0%BF%D0%B5%D1%81%D0%BD%D1%8F-%D0%BE-%D0%B1%D0%B5%D0%B7%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D0%BD%D0%BE%D0%B9-%D0%BB%D1%8E%D0%B1%D0%B2%D0%B8-%D0%BA-%D1%80%D0%BE%D0%B4%D0%B8%D0%BD%D0%B5/1103393546&quot;&gt;[iTunes]&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=VvaiXw6k4g8&quot;&gt;[Youtube]&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://rateyourmusic.com/release/album/%D0%BD%D0%BE%D0%BB%D1%8C/%D0%BF%D0%B5%D1%81%D0%BD%D1%8F-%D0%BE-%D0%B1%D0%B5%D0%B7%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D0%BD%D0%BE%D0%B9-%D0%BB%D1%8E%D0%B1%D0%B2%D0%B8-%D0%BA-%D1%80%D0%BE%D0%B4%D0%B8%D0%BD%D0%B5/&quot;&gt;RYM&lt;/a&gt; tags: Folk Punk / Russian Folk Music&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Album:&lt;/strong&gt; Nol’, one of the youngest bands in the scene, skyrocketed into prominence with this topical album of nostalgic folk-punk, released just as the Soviet Union was about to collapse. Their popularity was short-lived, however, as their frontman, Fyodor Chistyakov, was imprisoned the following year for the attempted murder of his girlfriend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Sound:&lt;/strong&gt; Imagine the sound of classic rock’n’roll, but with the guitars largely replaced by accordion and mandolin. Basically, Leningrad’s answer to the Pogues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Lyrics:&lt;/strong&gt; Themes include post-Soviet depression (“Instead of a blue sky, there’s a grey ceiling”), coping strategies (“Having smoked hashish / Life is becoming beautiful”), and the ever-present legacy of authoritarianism (“You ask me why sometimes I’m silent / Why I don’t laugh, why I don’t smile / … / It’s just that I live on Lenin Street / And I freak out from time to time.”) &lt;a href=&quot;https://lyricstranslate.com/en/nol-lyrics.html&quot;&gt;[Some English translations here.]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Highlights:&lt;/strong&gt; &lt;strong&gt;[1]&lt;/strong&gt; “Этот русский Rock-n-roll” (“This Russian Rock’n’Roll”), &lt;strong&gt;[3]&lt;/strong&gt; “Улица Ленина” (“Lenin Street”), &lt;strong&gt;[5]&lt;/strong&gt; “Иду, курю” (“I Walk and Smoke”), &lt;strong&gt;[9]&lt;/strong&gt; “Человек и кошка” (“A Man and a Cat”)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See also:&lt;/strong&gt; DDT (ДДТ)’s 1989 album Я получил эту роль (&lt;em&gt;I Won This Role&lt;/em&gt;), a cynical hard-rock album also grappling with the imminent fall of the Soviet Union, full of song titles like “Don’t Shoot!” and “Revolution”. &lt;a href=&quot;https://open.spotify.com/album/2xwcGbC9GB5Dq8sSzbveA1&quot;&gt;[Spotify]&lt;/a&gt; &lt;a href=&quot;https://music.apple.com/us/album/%D1%8F-%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D0%BB-%D1%8D%D1%82%D1%83-%D1%80%D0%BE%D0%BB%D1%8C/1035340238&quot;&gt;[iTunes]&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=lqzHWiONDs4&amp;amp;list=PL6bMAI1kdnIPRRX8AnNT_gI84PydInsJp&quot;&gt;[Youtube]&lt;/a&gt; &lt;a href=&quot;(https://web.archive.org/web/20161129135108/http://russmus.net/band/31/#album-251)&quot;&gt;[English lyrics]&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;sources--further-reading&quot;&gt;Sources &amp;amp; Further reading&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Leningrad rock in general
    &lt;ul&gt;
      &lt;li&gt;https://www.rbth.com/arts/2013/08/24/how_soviet_underground_music_rocked_perestroika_29179.html&lt;/li&gt;
      &lt;li&gt;https://www.calvertjournal.com/features/show/6563/igor-mukhin-leningrad-photos-perestroika-viktor-tsoi&lt;/li&gt;
      &lt;li&gt;http://soviethistory.msu.edu/1985-2/the-leningrad-rock-scene/&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Individual bands
    &lt;ul&gt;
      &lt;li&gt;Аквариум [Aquarium]
        &lt;ul&gt;
          &lt;li&gt;http://www.nytimes.com/1987/04/09/arts/for-soviet-rock-musicians-glasnost-is-angst.html&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Кино [Kino]
        &lt;ul&gt;
          &lt;li&gt;https://www.rbth.com/arts/2014/06/20/viktor_tsoi_the_last_hero_of_russian_rock_37605.html&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Ноль [Nol]
        &lt;ul&gt;
          &lt;li&gt;https://therussianreader.com/2014/12/05/lenin-street/&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Key events&lt;/li&gt;
  &lt;li&gt;https://en.wikipedia.org/wiki/Tbilisi_Rock_Festival_(1980)&lt;/li&gt;
  &lt;li&gt;http://popkult.org/leningrad-rock-club/&lt;/li&gt;
&lt;/ul&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Reading List - 2016</title>
   
    <link href="http://alex.nisnevich.com/blog/2017/01/03/reading_list_2016.html"/>
   
   <updated>2017-01-03T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2017/01/03/reading_list_2016</id>
   
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://www.facebook.com/notes/alex-nisnevich/reading-list-2016/10153930614281685&quot;&gt;&lt;i&gt;[Originally posted on Facebook]&lt;/i&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Continuing the tradition, here’s what I read last year:&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★★★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Wind-Up-Bird Chronicle&lt;/i&gt; – Haruki Murakami (tr. Rubin)&lt;/div&gt;

&lt;p&gt;It’s almost two separate stories: a romantic drama about a fragile relationship and a gripping adventure through a surreal world. Either of them would make a good novel in itself, but Murakami’s brilliance lies in how perfectly he weaves the two together. This is the kind of novel where half the fun comes from figuring out the connections and the twisted logic underlying it all (a feeling I also had reading &lt;em&gt;House of Leaves&lt;/em&gt; last year). &lt;em&gt;[thanks Lily]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;War and Peace&lt;/i&gt; – Leo Tolstoy (tr. Pevear &amp;amp; Volokhonsky)&lt;/div&gt;

&lt;p&gt;Part sprawling epic novel, part meditation on the nature of history. Tolstoy gets inside his characters’ heads so well that by the end of the book, I felt like they were old friends. All throughout, and particularly in the “war” sections, he argues against the Great Man theory of history and proposes a bottom-up view of historical events. The resulting work is long and messy and arguably not even a “novel” at all, but at the same time it’s perfect for what it is. Isaac Babel said, “If the world could write by itself, it would write like Tolstoy,” and after reading War and Peace I couldn’t agree more. &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Travels with Herodotus&lt;/i&gt; – Ryszard Kapuściński (tr. Glowczewska)&lt;/div&gt;

&lt;p&gt;This is certainly an unusual concept for a book. Half of it is Kapuściński’s account of his journeys through India, China, and Sudan as a bright-eyed young Polish correspondent. In parallel, he recounts stories from Herodotus’s histories that his journeys remind him of. It’s remarkable how believable the characters in his retellings are: in the end, perhaps we’re not so different from the ancient Persians and Greeks. &lt;em&gt;[thanks Lily]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The New And Improved Romie Futch&lt;/i&gt; – Julia Elliott&lt;/div&gt;

&lt;p&gt;So, imagine “Flowers for Algernon” meets Moby-Dick. Now imagine it in a setting that’s halfway between Southern Gothic and Saunders-esque playful dystopia. And throw in surreal taxidermy, giant pigs, and Monsanto. &lt;em&gt;Romie Futch&lt;/em&gt; is all this and more.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Proper Study of Mankind&lt;/i&gt; – Isaiah Berlin&lt;/div&gt;

&lt;p&gt;No book on this list has affected the way I think as profoundly as this collection of essays. The biggest takeaway for me was his idea that there cannot be one single ideal to strive for (“value pluralism”). He traces this idea through history, from Vico to Herder, and along the way he presents fresh perspectives on Machiavelli and Tolstoy. Other highlights include his portraits of his Russian contemporaries and his argument against “scientific history”.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Collected Stories&lt;/i&gt; – Stefan Zweig (tr. Bell) &lt;/div&gt;

&lt;p&gt;I can’t believe that I hadn’t heard of Zweig before I got this book! These are 22 of the best short stories I’ve ever read, all set against a beautifully-written European landscape. The prose is gorgeous even in translation, and the characters display an immense amount of humanity. Highlights: “The Miracles of Life”, “The Governess”, “Compulsion”, “Fantastic Night”, “Mendel the Bibliophile”. &lt;em&gt;[thanks dad]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Excellent Sheep&lt;/i&gt; – William Deresiewicz [thanks Asali]&lt;/div&gt;

&lt;p&gt;In this book-length adaptation of his 2008 essay “The Disadvantages of an Elite Education”, Deresiewicz argues that the Ivy Leagues (and schools like them) are producing an educated elite  out of touch with the rest of the country. He forms his argument from both a detailed historical account of the American education system and hundreds of firsthand accounts from students. Unfortunately, he doesn’t offer much in the way of solutions (aside from the usual clichés), but it’s a timely critique. &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Book of Tea&lt;/i&gt; – Okakura Kakuzō&lt;/div&gt;

&lt;p&gt;Okakura presents a passionate defense of what he calls “tea-ism”, the philosophy of reveling in the aesthetics of the simple things in life. The book itself is an aesthetic marvel. Every paragraph is beautifully written, all the more remarkable given that Okakura learned English late in life.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Postmodernism for Beginners&lt;/i&gt; – Jim Powell and Joe Lee&lt;/div&gt;

&lt;p&gt;Corny title aside, the “for Beginners” series has been blowing me away with its intelligence and wit. I came in thinking of “postmodernism” as just a punchline, and now I’m ordering books by Baudrillard. Powell &amp;amp; Lee do a fantastic job of distilling key ideas of postmodern writers and artists and giving them context. [thanks Mel]&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Y.T.&lt;/i&gt; – Alexei Nikitin (tr. Jackson)&lt;/div&gt;

&lt;p&gt;A fascinating novella about a group of bored Ukrainian physics students who start a geopolitical role-playing game, and the consequences that follow. Nikitin’s portrayal of Ukraine in the 80s and the 2000s is by turns bleak and satirical, reminding me somewhat of Dovlatov.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Alice in Bed&lt;/i&gt; – Susan Sontag&lt;/div&gt;

&lt;p&gt;How do you make a play about somebody who spent her entire life in bed? Not the easiest premise to work with, but Sontag does a masterful job. There’s not really an overarching plot, but rather a series of vignettes, ranging from mundane family moments to a Lewis Carroll-esque tea party to a bizarre robbery scene. I would love to see a production of this play. I can only imagine what it looks like live.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Doomed City&lt;/i&gt; – Arkady and Boris Strugatsky (tr. Bromfield)&lt;/div&gt;

&lt;p&gt;I’ve been waiting for this translation for literally a decade. (Seriously, I was trying to translate it myself back in high school!) The premise is typical Strugatsky weirdness: a group of “volunteers” from throughout the 20th century find themselves in a city on an alien world for an “Experiment”. Challenges present themselves, some Lynchian (a mysterious red brick house travels around abducting citizens), others political (a fascist coup overthrows the government), all while a group of “Mentors” watch from the sidelines. This is the Strugatskys at their most political, challenging the Soviet experiment overtly rather than metaphorically, and it’s easy to see why it wasn’t published until after perestroika.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Stranger&lt;/i&gt; – Albert Camus (tr. Gilbert)&lt;/div&gt;

&lt;p&gt;“Mother died today. Or maybe yesterday, I don’t know.” &lt;em&gt;L’Étranger&lt;/em&gt; has a structure reminiscent of &lt;em&gt;Crime and Punishment&lt;/em&gt; (part 1 is the crime, part 2 is the punishment), but unlike Raskolnikov, Meursault isn’t a philosopher. He doesn’t want to be the next Napoleon. He simply doesn’t care: not about his society, not about the people around him, not about the necessity of crying at his mother’s funeral. It’s not easy to convey Meursault’s perspective while still making him a sympathetic character, but Camus somehow accomplishes this.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;101 Things I Learned in Architecture School&lt;/i&gt; – Matthew Frederick&lt;/div&gt;

&lt;p&gt;Another book with a cheesy title that taught me much more than I expected. Frederick cleverly illustrates concepts such as positive/negative space, &lt;em&gt;parti&lt;/em&gt;, and denial/reward. While the lessons are all nominally about architecture, a lot of the core concepts seem to be applicable to, for example, music, as well.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Soul of a New Machine&lt;/i&gt; – Tracy Kidder&lt;/div&gt;

&lt;p&gt;The most surprising part of reading &lt;em&gt;Soul of a New Machine&lt;/em&gt; was how little things have changed since 1981. Kidder’s depiction of Data General employees, their frustrations and motivations, is spot-on my experience at medium-to-large startups. Some of the characters reminded me so much of former coworkers that it felt uncanny. The more technical passages are sometimes dull (Kidder wrote it for an audience that understood very little about computers), but it’s still a worthwhile read.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;The Word for World is Forest&lt;/i&gt; – Ursula Le Guin&lt;/div&gt;

&lt;p&gt;This is Le Guin at her least subtle. The characters tend to be one-dimensional and the plot doesn’t offer many surprises. But as a hard-hitting anti-colonial polemic, it certainly delivers. I especially enjoyed her exploration of the effects of communication and the depiction of dreaming among the Athsheans.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Bernie&lt;/i&gt; – Ted Rall&lt;/div&gt;

&lt;p&gt;A surprisingly well-done graphic novel biography of Bernie Sanders. The section detailing the rise of the Democratic Leadership Council is a fascinating bit of history that I wasn’t aware of.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Death with Interruptions&lt;/i&gt; – Jose Saramago (tr. Costa)&lt;/div&gt;

&lt;p&gt;The first half is solid, full of that gentle yet piercing satire that I like in Saramago. The second half just seemed to drag on without ever reaching anything satisfactory.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;City of Tiny Lights&lt;/i&gt; – Patrick Neale&lt;/div&gt;

&lt;p&gt;A neat little noir tale transported into modern multicultural London. Neale’s levelheaded satire of the War on Terror is surprisingly prescient for 2005.&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★★&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Farewell to the God of Plague&lt;/i&gt; – Miriam Gross&lt;/div&gt;

&lt;p&gt;A comprehensive, if at times dry, look into Mao’s campaigns to eradicate schistosomiasis. Gross offers a nuanced portrayal of the responses of various classes of society to the campaign, challenging both Chinese and Western conventional narratives. &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/p&gt;

&lt;h3 style=&quot;margin: 1.5em 0&quot;&gt;★★½&lt;/h3&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Harry Potter and the Cursed Child&lt;/i&gt; – Jack Thorne&lt;/div&gt;

&lt;p&gt;I’m not sure if this play was really necessary. It’s certainly fun to see this world again, but the plot is quite silly and the characters are (with a couple major exceptions) pretty weak.&lt;/p&gt;

&lt;div style=&quot;margin-top: 1.5em; font-size: 1.1em; font-weight: bold;&quot;&gt;&lt;i&gt;Once Upon a Number&lt;/i&gt; – John Allen Paulos&lt;/div&gt;

&lt;p&gt;A collection of pieces about the relationship between mathematics and stories. Some chapters are alright, but I didn’t feel like I really learned anything from it. &lt;em&gt;[thanks Tikhon]&lt;/em&gt;&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Purple State Electoral Nightmare Scenario</title>
   
    <link href="https://alexnisnevich.github.io/one-weird-electoral-trick/"/>
   
   <updated>2016-10-22T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2016/10/22/purple_state_electoral_nightmare_scenario</id>
   
    <content type="html">See https://alexnisnevich.github.io/one-weird-electoral-trick/</content>
   
 </entry>
 
 <entry>
   <title>In Search of a New Musical Genre, Using Spotify Data</title>
   
    <link href="https://nbviewer.jupyter.org/github/AlexNisnevich/blog/blob/master/_notebooks/exploring-music-genres.ipynb"/>
   
   <updated>2016-08-12T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2016/08/12/in_search_of_a_new_musical_genre</id>
   
    <content type="html">See https://nbviewer.jupyter.org/github/AlexNisnevich/blog/blob/master/_notebooks/exploring-music-genres.ipynb</content>
   
 </entry>
 
 <entry>
   <title>Secrets of natural language UIs: Translating English into computer actions</title>
   
    <link href="http://alex.nisnevich.com/blog/2016/07/01/secrets_of_natural_language_uis.html"/>
   
   <updated>2016-07-01T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2016/07/01/secrets_of_natural_language_uis</id>
   
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://github.com/turian&quot;&gt;Joseph Turian&lt;/a&gt; and I gave a talk at &lt;a href=&quot;http://conferences.oreilly.com/strata/hadoop-big-data-ca&quot;&gt;Strata 2016&lt;/a&gt; entitled &lt;a href=&quot;http://conferences.oreilly.com/strata/hadoop-big-data-ca/public/schedule/detail/47360&quot;&gt;“Secrets of natural language UIs: Translating English into computer actions”&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here’s a video:&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/lnV2JnNBM1I&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;In the talk, we argue that natural-language UIs can enable users to interact with complicated data in a way that traditional UIs cannot. As an example, we discuss the implementation of &lt;a href=&quot;http://venturebeat.com/2014/01/24/salesforce-prize-winner-upshot-teases-investors-with-voice-triggered-analytics/&quot;&gt;UPSHOT&lt;/a&gt;’s English-to-SQL interface.&lt;/p&gt;

&lt;p&gt;Getting a little more hands-on, I give a quick tutorial &lt;em&gt;(starting around 7:20)&lt;/em&gt; on &lt;a href=&quot;https://en.wikipedia.org/wiki/Combinatory_categorial_grammar&quot;&gt;CCG (combinatory categorial grammar)&lt;/a&gt; and basic &lt;a href=&quot;https://en.wikipedia.org/wiki/Lambda_calculus&quot;&gt;lambda calculus&lt;/a&gt;, finally culminating into a complete semantic parse of the example phrase &lt;em&gt;“top 5 opportunities in California by amount”&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;/blog/images/strata-2016-parse.jpg&quot; alt=&quot;Semantic CCG parse of &amp;quot;top 5 opportunities in California by amount&amp;quot;&quot; style=&quot;width: 560px;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Leading up to the talk, we released &lt;strong&gt;&lt;a href=&quot;https://github.com/Workday/upshot-montague&quot;&gt;montague&lt;/a&gt;&lt;/strong&gt;, a little CCG semantic parsing library for Scala that emphasizes simplicity and expressibility. &lt;strong&gt;montague&lt;/strong&gt; is an open-source version of the CCG parser that &lt;a href=&quot;https://github.com/Workday/upshot-montague#background&quot;&gt;powered UPSHOT’s English-to-SQL translation functionality&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you’ve ever wanted to try building a natural-language interface of your own but have been intimidated by the current NLP ecosystem, I think that &lt;strong&gt;montague&lt;/strong&gt; could be a great place to start. Check out the &lt;a href=&quot;https://github.com/Workday/upshot-montague/tree/master/src/main/scala/com/workday/montague/example&quot;&gt;&lt;tt&gt;examples&lt;/tt&gt; package&lt;/a&gt; (and &lt;a href=&quot;https://github.com/Workday/upshot-montague#getting-started&quot;&gt;its documentation&lt;/a&gt;) to see what programs written with &lt;strong&gt;montague&lt;/strong&gt; look like.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>How many closed lambda-calculus terms are there of a given length?</title>
   
    <link href="http://alex.nisnevich.com/blog/2016/05/19/how_many_lambda_terms_are_there.html"/>
   
   <updated>2016-05-19T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2016/05/19/how_many_lambda_terms_are_there</id>
   
    <content type="html">&lt;p&gt;&lt;em&gt;&lt;strong&gt;Update (6/2/16):&lt;/strong&gt; Reddit user &lt;a href=&quot;https://www.reddit.com/u/julesjacobs&quot;&gt;u/julesjacobs&lt;/a&gt; has managed to find &lt;a href=&quot;https://www.reddit.com/r/math/comments/4kftx8/how_many_closed_lambdacalculus_terms_are_there_of/d3f4b52&quot;&gt;an elegant recurrence relation for this problem&lt;/a&gt;! As it turns out, my approach slightly undercounted the number of terms (for length 10, I calculated 893 terms, while the true answer is 896), but was correct for N&amp;lt;10.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Fun fact: there are 147677899847908815123862539373715632995852464035157843195311323850864810096812226643364709183774473672545 terms of length 100.&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I thought of this question a few months ago, and, as far as I can tell, nobody has really done any work on it before. And I’m not surprised – it doesn’t seem to really be a question of any practical significance, just an idle curiosity.&lt;/p&gt;

&lt;p&gt;But hey, let’s think it though. How would we go about calculating the number of distinct &lt;a href=&quot;https://en.wikipedia.org/wiki/Lambda_calculus#Free_and_bound_variables&quot;&gt;closed lambda terms&lt;/a&gt; of length N?&lt;/p&gt;

&lt;h3 id=&quot;defining-the-problem&quot;&gt;Defining the problem&lt;/h3&gt;

&lt;p&gt;To start, let’s lay down some ground rules for what we mean by “distinct terms”. First off, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;λx.x&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;λy.y&lt;/code&gt; are clearly equivalent for our purposes – no question about that. So terms that are alpha-equivalent (in other words, equivalent to each other if bound variables are changed) should not be treated as distinct.&lt;/p&gt;

&lt;p&gt;Also, it’s clear that we must follow some kind of conventions, or else we’d get some silly “distinct” terms: for example, we really shouldn’t treat &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(MN)P&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(MNP)&lt;/code&gt; as different terms! Somewhat arbitrarily, I chose to follow the &lt;a href=&quot;https://en.wikipedia.org/wiki/Lambda_calculus#Notation&quot;&gt;notation rules listed on Wikipedia&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Outer parentheses are always dropped: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(MN)&lt;/code&gt; -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MN&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Applications are assumed to be left associative: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;((MN)P)&lt;/code&gt; -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MNP&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;The body of an abstraction extends as far right as possible: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;λx.(MN)&lt;/code&gt; -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;λx.MN&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;A sequence of abstractions is contracted: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;λx.λy.λz.N&lt;/code&gt; -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;λxyz.N&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally, as far as counting length goes, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;λ&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;)&lt;/code&gt;, and variables count as a single character each, and we will ignore any whitespace.&lt;/p&gt;

&lt;h3 id=&quot;time-for-some-counting&quot;&gt;Time for some counting!&lt;/h3&gt;

&lt;p&gt;So, now that we’ve fully stated what it is we’re counting, let’s give it a shot.&lt;/p&gt;

&lt;p&gt;The shortest possible closed lambda term is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;λa.a&lt;/code&gt;, so there are none of length 3 or less, and only one distinct term of length 4.&lt;/p&gt;

&lt;p&gt;How about length 5? We only have space for a single λ, and we can either take one or two variables. In the former case, we have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;λa.aa&lt;/code&gt;, and in the latter case, we have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;λab.a&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;λab.b&lt;/code&gt;. So, there are 3 terms of length 5.&lt;/p&gt;

&lt;p&gt;How about length 6? It’s starting to get harder to count them all, but some work gives us 8 in total:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;λa.aaa,
λab.aa,
λab.ab,
λab.ba,
λab.bb,
λabc.a,
λabc.b,
λabc.c&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And so on. As long as we stick to terms with a single λ and no parentheses, it doesn’t actually seem too hard to quantify the number of distinct terms (for example, for length 6 there are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1^3 + 2^2 + 3^1 = 8&lt;/code&gt; and for length 7 there are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1^4 + 2^3 + 3^2 + 4^1 = 22&lt;/code&gt; such terms).&lt;/p&gt;

&lt;p&gt;But things get tricky once we get to length 8, because suddenly we’re faced with terms that look like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;λa.a(aa)&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;λa.aλb.a&lt;/code&gt;. Eep, accounting for these definitely makes our task a lot trickier.&lt;/p&gt;

&lt;p&gt;Eventually I gave up on trying to find a closed form that accounts for all of these cases, and decided to take the computational approach.&lt;/p&gt;

&lt;h3 id=&quot;the-computational-approach&quot;&gt;The computational approach&lt;/h3&gt;

&lt;p&gt;First I wrote a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;find_patterns&lt;/code&gt; function that finds all possible “λ patterns” up to a given length (where a “λ pattern” is a λ term with all variable slots instead occupied by the symbol &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It works by alternating &lt;em&gt;Search&lt;/em&gt; and &lt;em&gt;Simplify&lt;/em&gt; steps until the results stabilize. In the &lt;em&gt;Search&lt;/em&gt; step, patterns of length N are created through &lt;em&gt;abstraction&lt;/em&gt; (creating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;λX.[term]&lt;/code&gt; for every term of length N-3) or through &lt;em&gt;application&lt;/em&gt; (combining terms &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;M&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;N&lt;/code&gt; into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(MN)&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(NM)&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;In the &lt;em&gt;Simplify&lt;/em&gt; step, the &lt;a href=&quot;https://en.wikipedia.org/wiki/Lambda_calculus#Notation&quot;&gt;notation rules listed above&lt;/a&gt; are applied to all candidate patterns, potentially reducing their lengths.&lt;/p&gt;

&lt;p&gt;Here’s what the function looks like in Ruby:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;find_patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;X&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# in &quot;patterns&quot;, X is a placeholder for variables&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Alternate between search and simplify steps many times &lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# to build up the set of candidate patterns&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Search&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# we need to go up to max_n + 2 &lt;/span&gt;
                                  &lt;span class=&quot;c1&quot;&gt;# to get accurate patterns up to max_n&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Building up new terms through abstraction&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;λX.&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# Building up new terms through application&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
              &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;(&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;)&quot;&lt;/span&gt;
              &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;(&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;)&quot;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;uniq!&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Simplify (https://en.wikipedia.org/wiki/Lambda_calculus#Notation)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Note: the (M N) P =&amp;gt; M N P rule is harder to encode, &lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#       but fortunately doesn't come up in N&amp;lt;=10 at all!&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;simplified_patterns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;simplified_patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map!&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gsub!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/λ(X+)\.λ(X+)\./&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'λ\1\2.'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gsub!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/^\((.*)\)$/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'\1'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gsub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/^\((.*)\)$/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'\1'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;balanced_parentheses?&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gsub!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/\.\((.*)\)/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'.\1'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gsub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/\.\((.*)\)/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'.\1'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;balanced_parentheses?&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;uniq!&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Reassign to buckets by length&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;simplified_patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;keep_if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_n&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Filter out patterns that cannot be combinators (don't start with λ)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;keep_if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start_with?&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;λ&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]}]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;After &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;find_patterns&lt;/code&gt; runs, it returns a dictionary mapping each length to a list of candidate patterns. For example, the patterns for length 8 are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[&quot;λX.X(XX)&quot;, &quot;λX.XXXXX&quot;, &quot;λX.XλX.X&quot;, &quot;λXX.XXXX&quot;, &quot;λXXX.XXX&quot;, &quot;λXXXX.XX&quot;, &quot;λXXXXX.X&quot;]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Each pattern is then passed into a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;combinators_by_pattern&lt;/code&gt; method that finds all possible closed terms that follow that pattern, by first filling in the variable bounds and then filling in the remaining slots in every possible way:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;combinators_by_pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# First fill in variable bounds&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bound_variables&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;next_bound_variable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;`&quot;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# a - 1&lt;/span&gt;

  &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/λ(X+)\./&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; 
      &lt;span class=&quot;n&quot;&gt;vars&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chars&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vars&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;next_bound_variable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;next!&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;bound_variables&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vars&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chars&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sub!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/λ(X+)\./&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;λ&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vars&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Then fill in the slots&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# NOTE: λ expressions inside parentheses pose a challenge for this naive&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#       implementation of &quot;bounded variables&quot;. But fortunately it's not an issue in N&amp;lt;=10!&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;combinators&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;combinators&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;X&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;X&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;combinators&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;combinators&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bound_variables&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# has this variable appeared yet?&lt;/span&gt;
                                      &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;X&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;combinators&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This little program is definitely not 100% correct – it (1) doesn’t properly handle λ expressions inside parentheses, and (2) doesn’t follow the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(MN)P&lt;/code&gt; -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MNP&lt;/code&gt; simplification rule – but neither of those cases come up in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;N&amp;lt;=10&lt;/code&gt; case, so I’m reasonably confident in the results I’ve gotten up to that point.&lt;/p&gt;

&lt;p&gt;Also, the runtime complexity is exponential, so it can’t really run for much higher &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;N&lt;/code&gt; anyway!&lt;/p&gt;

&lt;p&gt;Regardless of these flaws, I’ve put the code, along with some results, &lt;a href=&quot;https://github.com/AlexNisnevich/lambda-terms&quot;&gt;up on GitHub&lt;/a&gt;, for anyone who wants to play with it.&lt;/p&gt;

&lt;h3 id=&quot;a260661&quot;&gt;A260661&lt;/h3&gt;

&lt;p&gt;The folks at the Online Encyclopedia of Integer Sequences have been kind enough to accept my humble sequence of values for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1 &amp;lt;= N &amp;lt;= 10&lt;/code&gt; as Sequence &lt;a href=&quot;https://oeis.org/A260661&quot;&gt;A260661&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Though the entry doesn’t have as many terms as I would have liked to calculate, I’m pretty excited about my first OEIS entry. I feel like I’ve finally “made it” in some sense as a hobbyist mathematician.&lt;/p&gt;

&lt;h3 id=&quot;exercises-for-the-reader&quot;&gt;Exercises for the reader&lt;/h3&gt;

&lt;p&gt;I’ve reached the limit of what I can do with this problem, but there are still things that I’m curious about!&lt;/p&gt;

&lt;p&gt;First and foremost, finding any accurate results at all for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;N &amp;gt; 10&lt;/code&gt; would be interesting. I have some preliminary results from my script (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;11: 3678, 12: 16299, 13: 77108&lt;/code&gt;), but I’m hesitant to trust them due to the issues mentioned above.&lt;/p&gt;

&lt;p&gt;A real breakthrough would come if anyone can find a way to accurately compute results in faster-than-exponential time. In particular, I’m not ruling out a possible closed-form solution, although it would have to be pretty complex to encompass all of the notational rules.&lt;/p&gt;

&lt;p&gt;Happy hunting!&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Reading List - 2015</title>
   
    <link href="http://alex.nisnevich.com/blog/2016/01/02/reading_list_2015.html"/>
   
   <updated>2016-01-02T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2016/01/02/reading_list_2015</id>
   
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://www.facebook.com/alex.nisnevich/posts/10153269546231828&quot;&gt;&lt;i&gt;[Originally posted on Facebook]&lt;/i&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Continuing the tradition, here are the book I read in 2015:&lt;/p&gt;

&lt;h3&gt;★★★★★&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Tenth of December&lt;/em&gt; - George Saunders &lt;em&gt;[thanks Danielle]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;House of Leaves&lt;/em&gt; - Mark Z. Danielewski &lt;em&gt;[thanks James]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;The Secret History&lt;/em&gt; - Donna Tartt&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;The Handmaid’s Tale&lt;/em&gt; - Margaret Atwood &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-1&quot;&gt;★★★★½&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Freedom&lt;/em&gt; - Jonathan Franzen &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;The Gigantic Beard That Was Evil&lt;/em&gt; - Stephen Collins &lt;em&gt;[thanks Danielle]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Infinite City&lt;/em&gt; - Rebecca Solnit &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;I Am a Strange Loop&lt;/em&gt; - Douglas Hofstadter&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;“The Cheater’s Guide to Love”&lt;/em&gt; - Junot Díaz&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;The Brief Wondrous Life of Oscar Wao&lt;/em&gt; - Junot Díaz&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;The Pleasures and Sorrows of Work&lt;/em&gt; - Alain de Botton &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Summa Technologiae&lt;/em&gt; - Stanisław Lem&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-2&quot;&gt;★★★★&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;They Shoot Horses, Don’t They?&lt;/em&gt; - Horace McCoy &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Speak, Memory&lt;/em&gt; - Vladimir Nabokov &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;The Discovery of Neptune&lt;/em&gt; - Morton Grosser &lt;em&gt;[thanks Tikhon]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;A Visit From the Goon Squad&lt;/em&gt; - Jennifer Egan &lt;em&gt;[thanks Asali]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Marx’s Capital Illustrated&lt;/em&gt; - David Smith and Phil Evans&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Men Explain Things to Me&lt;/em&gt; - Rebecca Solnit&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;The Dead Mountaineer’s Inn&lt;/em&gt; - Arkady and Boris Strugatsky&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Everyman&lt;/em&gt; - Philip Roth &lt;em&gt;[thanks mom &amp;amp; dad]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Doing Good Better&lt;/em&gt; - William MacAskill&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Arabs &amp;amp; Israel for Beginners&lt;/em&gt; - Ron David&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-3&quot;&gt;★★★½&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Gentlemen of the Road&lt;/em&gt; - Michael Chabon&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-4&quot;&gt;★★★&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;A Portrait of the Artist as a Young Man&lt;/em&gt; - James Joyce&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Geek Sublime&lt;/em&gt; - Vikram Chandra &lt;em&gt;[thanks Danielle]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;New Urbanism and American Planning&lt;/em&gt; - Emily Talen&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-5&quot;&gt;★★½&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Iterating Grace&lt;/em&gt; - Anonymous&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’_ve also started subscribing to a few magazines this year - Nautilus and &lt;a href=&quot;http://twolinespress.com/two-lines-journal/&quot;&gt;Two Lines Press&lt;/a&gt; - and both have been incredible reads so far. I read an issue of Lapham’s Quarterly (VIII.3 - “Philanthropy”) that had a huge influence on me, and I wish I had the time to subscribe to it too, but it’s such a dense magazine that reading it four times a year would leave no time for books 🙁&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Postmortem - Asshole Transit Bureaucrat 2015</title>
   
    <link href="http://alex.nisnevich.com/blog/2015/10/18/asshole_transit_bureaucrat_postmortem.html"/>
   
   <updated>2015-10-18T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2015/10/18/asshole_transit_bureaucrat_postmortem</id>
   
    <content type="html">&lt;p&gt;-&amp;gt;&lt;a href=&quot;https://web.archive.org/web/20160820081148/http://ludumdare.com/compo/ludum-dare-33/?action=preview&amp;amp;uid=3353&quot;&gt;&lt;img src=&quot;http://i.imgur.com/sMHxmYf.png?2&quot; alt=&quot;Asshole Transit Bureaucrat 2015&quot; /&gt;&lt;/a&gt;&amp;lt;-&lt;/p&gt;

&lt;p&gt;This postmortem’s been a long time coming, in part because I don’t know if I can call my team’s submission to this last Ludum Dare, &lt;a href=&quot;https://web.archive.org/web/20160820081148/http://ludumdare.com/compo/ludum-dare-33/?action=preview&amp;amp;uid=3353&quot;&gt;Asshole Transit Bureaucrat 2015&lt;/a&gt;, a &lt;em&gt;game&lt;/em&gt;, per se. But we did end up scoring well in a few categories, so perhaps I was a bit too harsh. Let’s discuss what went right and wrong.&lt;/p&gt;

&lt;h2 id=&quot;what-went-wrong&quot;&gt;What went wrong&lt;/h2&gt;

&lt;h3 id=&quot;too-much-conceptual-complexity&quot;&gt;Too much conceptual complexity&lt;/h3&gt;

&lt;p&gt;This is a bit of a common problem with me and Ludum Dare: it’s been an issue with &lt;a href=&quot;https://web.archive.org/web/20160821020212/http://ludumdare.com/compo/ludum-dare-20/?action=preview&amp;amp;uid=3353&quot;&gt;It’s Not Easy Being Muammar&lt;/a&gt; and, to some extent, &lt;a href=&quot;http://alex.nisnevich.com/blog/2014/05/07/asteroid_tycoon_postmortem.html&quot;&gt;Asteroid Tycoon&lt;/a&gt;. In this case, the complexity bit us in two ways.&lt;/p&gt;

&lt;p&gt;On the one hand, it was hard for players to understand what was going on. The goal was already a difficult one to wrap your head around (make a transit system &lt;em&gt;less&lt;/em&gt; efficient? what does that &lt;em&gt;mean&lt;/em&gt;?), but the fact that the only thing you were able to control was the order of stops in a bus route made it even worse. Add to that the fact that there were some subtle issues with the buses that prevented them from always following their prescribed routes, and players were left completely clueless.&lt;/p&gt;

&lt;p&gt;On the other hand, it was exceptionally difficult for us to actually make the damn thing work. We ended up only being able to spend maybe 20% of our time working on gameplay because just getting the traffic simulation part working took so much time and energy. It definitely shows.&lt;/p&gt;

&lt;h3 id=&quot;homogenous-team&quot;&gt;Homogenous team&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://alex.nisnevich.com/blog/2014/09/27/shattered_worlds_postmortem.html&quot;&gt;Last time around&lt;/a&gt;, we had a healthy mix of programmers, designers, artists, and musicians. This time, we just had three programmers and a part-time musician. To make matters worse, we were all the &lt;em&gt;same kind&lt;/em&gt; of programmer, more or less – all functional programming nerds – and so we all tended to approach problems the same way.&lt;/p&gt;

&lt;p&gt;This experience has really hammered into me the importance of a diverse team: it would have been great to have some people on our team looking at problems from the player’s perspective (something the three of us didn’t do nearly enough of).&lt;/p&gt;

&lt;h3 id=&quot;poor-task-management&quot;&gt;Poor task management&lt;/h3&gt;

&lt;p&gt;Tying into the above, because Greg, Tikhon, and I all have similar interests, we all wanted to work on similar problems. And none of us is particularly good at being a taskmaster. As a result, task management was a constant issue.&lt;/p&gt;

&lt;p&gt;One of us spent a full day working on a level editor, just because it was a fun problem, even though we couldn’t even integrate it into the final game. We kept ironing out theoretical edge cases in the engine because it was technically interesting, even though we had no working game to plug it into yet.&lt;/p&gt;

&lt;p&gt;When we did split up tasks (one of us worked on the engine, another on the game UI), we didn’t sync up about the interface we wanted well enough, with the result being that integrating all our individual work together was a minor nightmare.&lt;/p&gt;

&lt;p&gt;All of this could have been avoided if we’d been a little more principled about how we approached tasks. In the past, Jordan and I were usually the once to figure out how to manage the team. Maybe I need to start learning how to do that on my own.&lt;/p&gt;

&lt;h2 id=&quot;what-went-right&quot;&gt;What went right&lt;/h2&gt;

&lt;h3 id=&quot;sticking-to-it&quot;&gt;Sticking to it&lt;/h3&gt;

&lt;p&gt;As the last few hours of the jam approached, it was clear that, no matter what we did, we wouldn’t have a finished game. At best we’d have three levels that &lt;em&gt;kinda, sorta&lt;/em&gt; worked, a hastily-put-together and not quite complete interface, and an abrupt ending.&lt;/p&gt;

&lt;p&gt;The big question wasn’t how to finish the game. It was whether to even submit it or just not bother.&lt;/p&gt;

&lt;p&gt;I was unsure initially, but now I’m glad we submitted it, incomplete though it was. We got a ton of interesting feedback, and ended up scoring #126 in Audio and #127 in Innovation. It’s not our best yet, but hey - it’s better than nothing!&lt;/p&gt;

&lt;h3 id=&quot;the-best-possible-aesthetics-given-our-constraints&quot;&gt;The best possible aesthetics given our constraints&lt;/h3&gt;

&lt;p&gt;The look and feel of the game are something I’m proud of, especially given what we managed to accomplish with no artists or full-time musicians.&lt;/p&gt;

&lt;p&gt;We made some good tradeoffs. For art, rather than taking the easy way out and making a purely text-oriented game (as I’ve &lt;a href=&quot;https://web.archive.org/web/20160818212509/http://www.ludumdare.com/compo/ludum-dare-27/?action=preview&amp;amp;uid=3353&quot;&gt;done&lt;/a&gt; &lt;a href=&quot;https://web.archive.org/web/20160818213213/http://ludumdare.com/compo/ludum-dare-22/?action=preview&amp;amp;uid=3353&quot;&gt;before&lt;/a&gt;), we painstakingly put together an almost-hypnotic minimalist traffic simulator. It’s a lot of fun to look at, gameplay aside.&lt;/p&gt;

&lt;p&gt;And for music, we only had a few hours of our Matt’s time, so we had a choice. He could either make a couple really quick tracks, or a single complex and well-put-together track. We opted for the latter, and it was a good choice - it’s just one track that repeats over and over, but it’s a damn good one, and fits well with the aesthetics of the game.&lt;/p&gt;

&lt;h3 id=&quot;a-sense-of-playfulness&quot;&gt;A sense of playfulness&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Asshole Transit Bureaucrat 2015&lt;/em&gt; was never meant to be a game that takes itself seriously. We address a topical issue, but the whole game, from the premise to the in-game text, is completely and knowingly absurd.&lt;/p&gt;

&lt;p&gt;Not taking ourselves seriously helped us stay motivated when things weren’t going well, and I think it helped our players go easy on us, too.&lt;/p&gt;

&lt;h2 id=&quot;what-went-ok&quot;&gt;What went OK&lt;/h2&gt;

&lt;h3 id=&quot;using-elm&quot;&gt;Using Elm&lt;/h3&gt;

&lt;p&gt;Well, this is a tricky one. &lt;a href=&quot;http://elm-lang.org/&quot;&gt;Elm&lt;/a&gt; is my favorite programming language right now, and I was really looking forward to finally getting to use it in a Ludum Dare. But, as much as I enjoy working with it, I’m not sure whether it actually made our lives any easier (as compared to using JavaScript, which we’ve always done before).&lt;/p&gt;

&lt;p&gt;The main problem with using a lesser-known language in a hackathon is the lack of frameworks and libraries that can simplify your task. In past Ludum Dares, I’ve managed to offload a lot of the complexity of my games onto libraries, whether it was &lt;a href=&quot;&quot;&gt;rot.js&lt;/a&gt; for &lt;em&gt;10 Second Roguelike&lt;/em&gt;, &lt;a href=&quot;https://github.com/bgrins/javascript-astar&quot;&gt;javascript-astar&lt;/a&gt; for the pathfinding in &lt;em&gt;Asteroid Tycoon&lt;/em&gt;, or &lt;a href=&quot;http://wellcaffeinated.net/PhysicsJS/&quot;&gt;PhysicsJS&lt;/a&gt; for &lt;em&gt;Shattered Worlds&lt;/em&gt;. With &lt;em&gt;Asshole Transit Bureaucrat 2015&lt;/em&gt;, we had to write everything from scratch (on top of the rather poorly-documented &lt;a href=&quot;https://github.com/sgraf812/elm-graph&quot;&gt;elm-graph&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;On the other hand, it’s not like there’s a lot of traffic simulation libraries in JavaScript anyway, so maybe the issue wasn’t so much our choice of language as our unusual concept.&lt;/p&gt;

&lt;p&gt;The FRP (functional reactive programming) paradigm neither helped nor hurt us, I think. In some ways, it made it easier to reason about the motion of vehicles to think of movement as a single function from State to State, but we could probably have gotten away with the object-oriented approach of treating each car as an a separate agent. It’s mostly a matter of preference.&lt;/p&gt;

&lt;p&gt;One thing I’d like to do differently next time is to prioritize the game over the language – in other words, use the language that makes the most sense for whatever the game concept is, be it Elm, JavaScript, or maybe something else entirely.&lt;/p&gt;

&lt;h2 id=&quot;in-conclusion&quot;&gt;In conclusion&lt;/h2&gt;

&lt;p&gt;All in all, this one didn’t go so well. But at least we learned something.&lt;/p&gt;

&lt;p&gt;Next time, hopefully we’ll be able to make a complete game again.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>The Impact of Donations to Bernie Sanders</title>
   
    <link href="http://alexnisnevich.github.io/bernie-donations/"/>
   
   <updated>2015-10-08T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2015/10/08/impact_of_donations_to_bernie_sanders</id>
   
    <content type="html">See http://alexnisnevich.github.io/bernie-donations/</content>
   
 </entry>
 
 <entry>
   <title>Reading List - 2014</title>
   
    <link href="http://alex.nisnevich.com/blog/2015/01/01/reading_list_2014.html"/>
   
   <updated>2015-01-01T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2015/01/01/reading_list_2014</id>
   
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://www.facebook.com/alex.nisnevich/posts/10152535243891828&quot;&gt;&lt;i&gt;[Originally posted on Facebook]&lt;/i&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I got back into reading fiction in 2014. Here’s what I read last year (as best I can remember):&lt;/p&gt;

&lt;h3&gt;★★★★★&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;The Amazing Adventures of Kavalier &amp;amp; Clay&lt;/em&gt; - Michael Chabon &lt;em&gt;[thanks Danielle]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Blindness&lt;/em&gt; - Jose Saramago &lt;em&gt;[thanks mom &amp;amp; dad]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;What We Talk About When We Talk About Love&lt;/em&gt; - Raymond Carver &lt;em&gt;[thanks Danielle]&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-1&quot;&gt;★★★★½&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;The Crying of Lot 49&lt;/em&gt; - Thomas Pynchon&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Hard to Be a God&lt;/em&gt; - Arkady and Boris Strugatsky (new Olena Bormashenko translation)&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;The Diamond Age&lt;/em&gt; - Neal Stephenson&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Definitely Maybe&lt;/em&gt; - Arkady and Boris Strugatsky (new unexpurgated edition)&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;A Heartbreaking Work of Staggering Genius&lt;/em&gt; - Dave Eggers&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;American Pastoral&lt;/em&gt; - Philip Roth &lt;em&gt;[thanks mom &amp;amp; dad]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Colorblind&lt;/em&gt; - Tim Wise &lt;em&gt;[thanks Mel]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Cain&lt;/em&gt; - Jose Saramago &lt;em&gt;[thanks mom &amp;amp; dad]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;The Magician’s Land&lt;/em&gt; - Lev Grossman&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;“The Death of Ivan Ilyich”&lt;/em&gt; - Leo Tolstoy&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Goodbye, Columbus&lt;/em&gt; - Philip Roth&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;The Search for Heinrich Schlögel&lt;/em&gt; - Martha Baillie &lt;em&gt;[thanks Elise]&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-2&quot;&gt;★★★★&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Omon Ra&lt;/em&gt; - Victor Pelevin&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Einstein’s Dreams&lt;/em&gt; - Alan Lightman &lt;em&gt;[thanks Natasha]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;A Dance With Dragons&lt;/em&gt; - George R R Martin&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Travesties&lt;/em&gt; - Tom Stoppard&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Grendel&lt;/em&gt; - John Gardner&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;The Ocean at the End of the Lane&lt;/em&gt; - Neil Gaiman &lt;em&gt;[thanks Danielle]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Bro&lt;/em&gt; - Vladimir Sorokin &lt;em&gt;[thanks mom &amp;amp; dad]&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Dubliners&lt;/em&gt; - James Joyce&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-3&quot;&gt;★★★½&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Absurdistan&lt;/em&gt; - Gary Shteyngart&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;The Box Man&lt;/em&gt; - Kobo Abe&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Is That a Fish in Your Ear?&lt;/em&gt; - David Bellos&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Ice&lt;/em&gt; - Vladimir Sorokin&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Seeing&lt;/em&gt; - Jose Saramago&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-4&quot;&gt;★★★&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;In the Beginning … Was the Command Line&lt;/em&gt; - Neal Stephenson&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;23,000&lt;/em&gt; - Vladimir Sorokin&lt;/li&gt;
&lt;/ul&gt;
</content>
   
 </entry>
 
 <entry>
   <title>JSON Requests with rust-http</title>
   
    <link href="http://alex.nisnevich.com/blog/2014/10/23/json_requests_with_rust_http.html"/>
   
   <updated>2014-10-23T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2014/10/23/json_requests_with_rust_http</id>
   
    <content type="html">&lt;p&gt;I’ve been playing around with &lt;a href=&quot;http://www.rust-lang.org/&quot;&gt;Rust&lt;/a&gt; a lot the past couple weeks. It’s a neat little language with some cool ideas (I particularly like its memory-safety features, though they take some getting used to), but sadly the ecosystem has some catching up to do.&lt;/p&gt;

&lt;p&gt;In particular, making HTTP requests in Rust is a bit of a mess right now. The closest Rust has to a working requests library at the moment is &lt;a href=&quot;https://github.com/chris-morgan/rust-http&quot;&gt;rust-http&lt;/a&gt;, which is not being maintained while its creator works on its forthcoming replacement, &lt;a href=&quot;https://github.com/teepee/teepee&quot;&gt;Teepee&lt;/a&gt;. Unfortunately, rust-http’s API is not the clearest to use and &lt;a href=&quot;http://www.rust-ci.org/chris-morgan/rust-http/doc/http/&quot;&gt;its documentation&lt;/a&gt; is rather sparse.&lt;/p&gt;

&lt;p&gt;The fun part, though, is that this means I’ve had to figure out a lot of its quirks myself.&lt;/p&gt;

&lt;p&gt;I’ve been working on writing a client for a JSON API and I needed to use rust-http to make lots of JSON requests and then parse their responses as JSON, with as convenient an interface as possible. Here’s what I came up with.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Disclaimer: I have no idea whether what follows is idiomatic Rust – I’ve been using Rust for less than a month – but it works for me.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Individual requests are encapsulated in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Request&lt;/code&gt; struct (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;method&lt;/code&gt; is a &lt;a href=&quot;http://www.rust-ci.org/chris-morgan/rust-http/doc/http/method/enum.Method.html&quot;&gt;Method&lt;/a&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;headers&lt;/code&gt; is an instance of &lt;a href=&quot;http://www.rust-ci.org/chris-morgan/rust-http/doc/http/headers/request/struct.HeaderCollection.html&quot;&gt;HeaderCollection&lt;/a&gt;):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HeaderCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Json&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;json_request&lt;/code&gt; method takes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Request&lt;/code&gt; struct and tries to make a JSON request with it, returning &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ok(Some(response: Json))&lt;/code&gt; if the response is valid JSON, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ok(None)&lt;/code&gt; if the response is not valid JSON, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Err(error)&lt;/code&gt; if the request failed in some way or did not receive a 200 OK response:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// Makes a JSON request with headers and body and expects a JSON response&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Returns one of:&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//    Ok(Some(response)) : successfully received JSON response&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//    Ok(None)           : response is not valid JSON&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//    Err(error)         : did not receive a 200 OK response&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;json_request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;application_json&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;MediaType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;application&quot;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;json&quot;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]);&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request_params&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.path&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.as_slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;path is not a valid URL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body_str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request_params&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.body&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body_str&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.as_bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RequestWriter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;RequestWriter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request_params&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.headers.content_length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.headers.content_type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;application_json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.headers.accept&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;*/*&quot;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.headers.user_agent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Rust&quot;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (some APIs require a User-Agent)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;header&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request_params&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.headers&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.headers&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.read_response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to send request&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.read_to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to read response&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// println!(&quot;{}: {}&quot;, response.status, response_text);&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.status&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nn&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Ok&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response_text&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.as_slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;          &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here’s an example of how this method can be used:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// see http://doc.rust-lang.org/serialize/json/ to learn how to serialize datatypes into Json&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// or pass your body as a raw Json string this way:&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;empty_body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;from_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// to learn how to add custom headers to a HeaderCollection, see:&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// http://www.rust-ci.org/chris-morgan/rust-http/doc/http/headers/request/struct.HeaderCollection.html&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;empty_headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;HeaderCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// available methods: http://www.rust-ci.org/chris-morgan/rust-http/doc/http/method/enum.Method.html&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;https://api.github.com/users/AlexNisnevich&quot;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;empty_headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;empty_body&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;json_request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.to_pretty_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;           &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;JSON parsing error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;Err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;         &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Request failed: {}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And yeah, that’s about it. It’s still nowhere near as nice as, say, Python’s &lt;a href=&quot;http://docs.python-requests.org/en/latest/&quot;&gt;requests&lt;/a&gt; library, but it certainly beats working directly with rust-http’s low-level interface.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://gist.github.com/AlexNisnevich/418be2ba34e130977f5c&quot;&gt;whole thing is on GitHub&lt;/a&gt; for your perusal.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Some Words on Untrusted</title>
   
    <link href="http://alex.nisnevich.com/blog/2014/10/17/some_words_on_untrusted.html"/>
   
   <updated>2014-10-17T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2014/10/17/some_words_on_untrusted</id>
   
    <content type="html">&lt;p&gt;It’s been about six months since &lt;a href=&quot;https://github.com/neunenak&quot;&gt;Greg&lt;/a&gt; and I released &lt;a href=&quot;http://untrustedgame.com&quot;&gt;Untrusted&lt;/a&gt; into the world, so I figure now’s as good a time as any to write something about how Untrusted came to be.&lt;/p&gt;

&lt;h2 id=&quot;it-all-started-with-a-hackathon&quot;&gt;It All Started With a Hackathon&lt;/h2&gt;

&lt;p&gt;Greg and I had wanted to make a game for a long time, and when we decided to participate in the spring 2013 CSUA hackathon at Berkeley, we figured it was as good a time as any.&lt;/p&gt;

&lt;p&gt;Greg had an idea for a game that would take place in a Unix filesystem, where gameplay would involve moving around the filesystem and doing different operations, and where the final level would be unbeatable unless you decompile the game and rewrite part of it. It was an interesting idea, and I’d still like to perhaps try it sometime, but we couldn’t really come up with how we would implement it. I really liked the last part of the idea, though – the bit about having to modify the code of the game to make it beatable. When we gave up on the original idea, I proposed making a game where the central idea is the the player is able to modify the underlying game code. Greg thought that sounded pretty cool, so we got to work.&lt;/p&gt;

&lt;h3 id=&quot;design&quot;&gt;Design&lt;/h3&gt;

&lt;p&gt;The first thing we had to settle was how broad the scope of this “hacking” mechanic should be. If the player had arbitrary access to all game code, that would (a) be a nightmare to get working correctly, and (b) give the player far too much power to really make it possible for the game to be much a challenge. We decided to start by letting the player be able to modify the code that initializes each level, and see where we could go from there. We were considering given the player progressively more and more power to edit different aspects of the game, but we couldn’t quite figure out how that would work (though I did later use that idea in the last level of the game, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;21_endOfTheLine&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Ok, so if the player could modify each level, what would the levels themselves be like. We decided to go with a roguelike style, because it would mean that we wouldn’t have to worry about graphics (though a surprising amount of work went into finding appropriate Unicode characters for things), the level maps could be represented pretty cleanly in code, there wouldn’t be too many moving parts, and, honestly, we both just really like roguelikes.&lt;/p&gt;

&lt;p&gt;One final design hurdle was how to further limit the player’s control over the code and not make all the puzzles too trivial. Since being able to edit all of the code to a level would be too much power, we decided to only make certain lines editable. Then, as an additional level of control, we added our validator mechanism (in retrospect, we probably could have done some more interesting puzzles involving validators, but mostly we’ve just used them for basic things like ensuring players don’t create extra exits).&lt;/p&gt;

&lt;p&gt;With these pieces in place, we had the bulk of our design ready.&lt;/p&gt;

&lt;h3 id=&quot;implementation&quot;&gt;Implementation&lt;/h3&gt;

&lt;p&gt;The first steps we took in our implementation was creating the map, making it be able to load a level definition, and making the player moveable. We used the rather neat &lt;a href=&quot;http://ondras.github.io/rot.js/hp/&quot;&gt;rot.js&lt;/a&gt; library, which provided some nice abstractions, though we didn’t end up using all that many features from it. After a couple hours of work, we had a Map class that could place objects though a simple API, as well as an interactive player object. It was time to move on to the meat of the game: the evaluator.&lt;/p&gt;

&lt;p&gt;Actually, the evaluation part is fairly simple: we just call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eval&lt;/code&gt;. Yup. We were considering building our own parser, but that would have taken way too much work for the hackathon and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eval&lt;/code&gt; worked well enough for our needs that we never changed it afterwards. Of course, we do some interesting things around it: we first evaluate the player-provided &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;startLevel&lt;/code&gt; method on a dummy map (that supports the map API but has no in-game effect and is not visible to the player) and run validators on it. We also introduced some basic security measures here and there, like making sure certain words couldn’t be used (this could be easily circumvented though – I go into some detail later about how we designed a more robust security system).&lt;/p&gt;

&lt;p&gt;Now that we had a map that could be initialized based on user-provided code, the final piece of the puzzle was creating the editor. This actually turned out to be the hardest part of the hackathon for us. We used &lt;a href=&quot;http://codemirror.net/&quot;&gt;CodeMirror 2&lt;/a&gt;, which supported &lt;em&gt;almost&lt;/em&gt; all of the features we wanted but was missing one big one that we had to do ourselves: uneditable lines and sections. Conceptually, this didn’t seem too bad, but in between figuring out all the CodeMirror events we needed to handle and doing the right bookkeeping at all times (so that the editable areas would never move to where they shouldn’t be) ended up being pretty tricky. We ended up having to make some compromises due to time pressure: for example, in the hackathon version of Untrusted, only one line can be edited at a time and you can’t create new lines, so if you want to write multiple lines you have to go through the annoying process of navigating to each new line yourself. True multi-line editing was such a difficult feature to get working right with respect to uneditable areas that it took another year before it was properly implemented in Untrusted.&lt;/p&gt;

&lt;p&gt;We had to cut some corners, but we finally had a working game engine. The rest of the time was mostly spent coming up with levels. I’ll admit that we were a bit uncreative (perhaps our creative juices were burned out by the amount of work it took to get the editor working), and so the first four levels we came up with all have a similar feel: you are surrounded by blocks and need to find a way to get through them to the exit, in the face of increasingly tricky constraints (these are, with some rearrangement and tweaking, still the first four levels of Untrusted).&lt;/p&gt;

&lt;p&gt;In an effort to inject a little bit of creativity into our game, Greg and I took some time off from coding and tried to each come up with a unique premise for an interesting level. I came up with an idea for a level with invisible traps that can be identified by coloring them (this became &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;05_minesweeper&lt;/code&gt;), while Greg came up with an idea for level where the player had to bind a function call to a keystroke to get through an area (this became the basis for the function phone and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;08_intoTheWoods&lt;/code&gt;). We only had a couple of minutes to demo our project, so at the last minute I added a cheat key to skip between levels, so that we could present only the most interesting levels when we did our demo.&lt;/p&gt;

&lt;h3 id=&quot;end-result&quot;&gt;End Result&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://alex.nisnevich.com/untr/hackathon/&quot;&gt;Here it is&lt;/a&gt;, in all of its rough hackathony glory.&lt;/p&gt;

&lt;p&gt;It seems a bit embarrassingly unpolished when I look back on it now, but the hackathon judges really liked our idea, and Greg and I ended up winning first place (and some swanky 27” monitors). Given the surprisingly positive reception we got, we decided to continue working on Untrusted until we felt it was complete enough to release.&lt;/p&gt;

&lt;p&gt;We certainly didn’t expect to work on it for another 14 months, though.&lt;/p&gt;

&lt;h2 id=&quot;the-next-year&quot;&gt;The Next Year&lt;/h2&gt;

&lt;p&gt;Over the next year, we worked on Untrusted at an unpredictable pace, sometimes getting an enormous amount of new content done quickly and sometimes taking long breaks as our real lives caught up to us (for most of this time, Greg was working at Meraki, while I was doing my Masters at Berkeley).&lt;/p&gt;

&lt;p&gt;Our first round of work on Untrusted, in the first few months after the hackathon, was primarily devoted to cleaning up our ugly hacked-together codebase, creating a build system and a better level file format (namely, our JSX format) to make our lives easier, and throwing out some ideas for new gameplay elements. Levels that we created during this time include &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;07_colors&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;15_exceptionalCrossing&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Between late August and early October, we worked in some polish, adding help and menu panes, sound effects, and progress saving via localStorage. During this time, we came up with the idea for the climax of the game (at least difficulty-wise), the robot levels.&lt;/p&gt;

&lt;p&gt;In November, Greg and I came up with our plan for how to organize the game. We decided to divide it into three chapters: the first would introduce the primary game mechanics, the second would stop holding the player’s hand and keep increasing the difficulty curve, and the third would break the rules we had previously established by introducing a bunch of new crazy things, culminating in a boss fight. We also came up with a barebones plot that was a bit cliche but worked for our purposes, and after trying a few different approaches to establishing the story, we decided that conveying it entirely through code comments would be a nice touch.&lt;/p&gt;

&lt;p&gt;Now that we finally had a blueprint for the game, progress went much quicker, and by December we had more or less finalized the first two chapters of the game. In the meantime, we also created the loading screen and credits and found most of the background music that we wanted (aside: &lt;a href=&quot;http://freemusicarchive.org/&quot;&gt;Free Music Archive&lt;/a&gt; is amazing).&lt;/p&gt;

&lt;p&gt;Around New Year’s, we posted our new Untrusted prototype on Facebook for our friends to playtest. One thing that we noticed was that, even after all of the security improvements we had tried to add over the past year, people still managed to find a bunch of different exploits that enabled them to arbitrarily skip levels. Now, we definitely feel that finding ways to break the system is a big part of what Untrusted is about, and we don’t want to discourage it entirely, but if you can find a single exploit that lets you get around every level, that’s not very fun. In light of this, we came up with a mechanism to completely prevent players from accessing methods we don’t want them to access.&lt;/p&gt;

&lt;h3 id=&quot;aside-private-methods-in-untrusted&quot;&gt;Aside: Private Methods in Untrusted&lt;/h3&gt;

&lt;p&gt;Our solution to this is actually kind of cool, so I wanted to go over it briefly.&lt;/p&gt;

&lt;p&gt;Player-accessible classes – namely, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;player&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dynamicObject&lt;/code&gt; – contain &lt;em&gt;exposed methods&lt;/em&gt; (that is, methods that the player is allowed to access) and &lt;em&gt;unexposed methods&lt;/em&gt; (private methods that we want to be able to access but not expose to the player). Since JavaScript doesn’t really have a notion of visibility, we’ve had to use workarounds.&lt;/p&gt;

&lt;p&gt;Our first approach was to simply have all unexposed methods begin with an underscore (_) and disallow that character in player-written code. This is not foolproof by any means, though, since there are a number of workarounds one could do to inject an underscore without explicitly typing one. We needed a better solution.&lt;/p&gt;

&lt;p&gt;What we eventually came up was this wrapper function for exposed methods:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;wrapExposedMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__game&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_callUnexposedMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__game&lt;/code&gt; is a reference to the Game object, which has a local variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__playerCodeRunning&lt;/code&gt; and the following methods:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_isPlayerCodeRunning&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__playerCodeRunning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_callUnexposedMethod&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__playerCodeRunning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;__playerCodeRunning&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;__playerCodeRunning&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, to enforce exposure of methods, all we need to do is (1) wrap all exposed methods with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wrapExposedMethod&lt;/code&gt; and (2) check &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__game._isPlayerCodeRunning()&lt;/code&gt; at the beginning of each unexposed method. For example, the Map class could have methods like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sampleExposedMethod&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;wrapExposedMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sampleUnexposedMethod&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__game&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_isPlayerCodeRunning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Forbidden method call: sampleUnexposedMethod()&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The neat thing about this approach is that exposed methods are still able to internally call unexposed methods with no problems, but each starting method call from the executed player code has to be an exposed method.&lt;/p&gt;

&lt;h3 id=&quot;anyway&quot;&gt;Anyway…&lt;/h3&gt;

&lt;p&gt;After taking some time off Untrusted to rest, we went back at it in February, and over the next two months we finished Chapter 3 and added some features our friends had requested a lot during playtesting, such as a notepad pane and the ability to share your solutions through automatic &lt;a href=&quot;http://gist.github.com&quot;&gt;Gist&lt;/a&gt; saving. The most requested feature, the ability to edit multiple lines at once (that is, the ability to expand and contract multi-line blocks sections in the editor, and to delete or copy multiple lines) was implemented by our friend &lt;a href=&quot;https://github.com/dmazin&quot;&gt;Dmitry&lt;/a&gt; (who, incidentally, also contributed a couple pieces of music). This was such a huge and difficult code contribution (due to the complicated interactions it had with the boundaries of editable sections) that we specifically credit him for “implementation of multiline editing” in our &lt;a href=&quot;https://github.com/AlexNisnevich/untrusted&quot;&gt;Acknowledgements&lt;/a&gt; section.&lt;/p&gt;

&lt;p&gt;Finally, at the beginning of April 2014, Untrusted was complete and ready to be shown to the world.&lt;/p&gt;

&lt;!---
Hackathon (early March 2013): 6 levels
March: first round of playtesting, code reorganization, build system
April: colors and exceptionalCrossing levels
May: dynamic objects
(June-July: break from Untrusted)
August: robot levels, help and menu panes, sound effects
September: platformer level, infinite loop prevention
October: code cleanup, localStorage
November: Chapter 1 complete, story and flavor text, teleporter level, loading screen
December: Chapter 2 complete, background music, credits, heavy-duty code cleanup and bug fixes
January 2014: second round of playtesting, security improvements
February: multiline editing, lasers level, gist saving, notepad pane
March: Chapter 3 complete, DOM and final levels, level versioning
April: final round of playtesting, release
--&gt;

&lt;h2 id=&quot;release--disaster&quot;&gt;Release &amp;amp; Disaster&lt;/h2&gt;

&lt;p&gt;When I &lt;a href=&quot;https://news.ycombinator.com/item?id=7547942&quot;&gt;posted Untrusted to Hacker News&lt;/a&gt; on the morning of April 7th, we decided we were aiming for maybe 10-20 upvotes, 50-100 if we were lucky.&lt;/p&gt;

&lt;p&gt;We hit the front page almost immediately. Then we rose to 10th place. Then 5th. Then all the way to the top.&lt;/p&gt;

&lt;p&gt;About an hour after the post, Untrusted began breaking: players were beating levels without advancing to the next level and getting stuck in loops. At first there were just a few isolated reports of this, but soon enough the game stopped working for everyone.&lt;/p&gt;

&lt;p&gt;There were now thousands of people trying to play Untrusted and it wasn’t working. It was a disaster.&lt;/p&gt;

&lt;p&gt;I did my best to track down the problem. It turned out that hosting Untrusted along with all of its music (a 200MB soundtrack) on my own VPS server was a terrible decision: the server hit its bandwidth cap within an hour and the hosting provider assumed that I was getting DDOSed and began rate-limiting all requests. Now, since Untrusted loads levels through AJAX calls, this meant that everyone playing the game at the time suddenly became sporadically (and soon, completely) unable to load levels. Even worse, we hadn’t considered the possibility of the AJAX level requests failing and had no built-in error handling to speak of. So, the game still acted as though the next level was loaded in these situations, causing levels to be overwritten by other levels and other bizarre bugs. Even worse than that, since level code was constantly written to localStorage, this meant that players’ game states were becoming corrupted to the point that even after this outage went away, the game would still be unplayable until they cleared localStorage.&lt;/p&gt;

&lt;p&gt;Yeah, it was bad.&lt;/p&gt;

&lt;p&gt;We had to act fast. For lack of a better idea, I moved everything over to GitHub Pages and set up a server-side redirect ASAP. Until the redirect started working, I had to work on damage control in Hacker News, apologizing profusely and directing people to our GH Pages URL (until I finished setting up GH Pages, I suggested that people could clone the repository themselves and play Untrusted locally – not the best thing to tell frustrated players but the best I could come up with at the time). I also temporarily special-cased some code in to detect when players’ localStorage` was messed up and fix it as unintrusively as possible.&lt;/p&gt;

&lt;p&gt;After the situation stabilized, we began the process of moving all the music over to Amazon CloudFront. (Theoretically GitHub Pages doesn’t have any bandwidth limits, but I didn’t want to surprise them with 5TB/month.) CloudFront handled the load it needed very well but ended up being a little expensive, so we added a small donation button and tried some tricks to reduce requests (like not requesting music while the game is muted).&lt;/p&gt;

&lt;p&gt;That night, Greg and I also rewrote the level loading system from scratch, so that this problem could never happen again. Instead of the game making AJAX requests for each level, all the levels are now packed into an array inside the minified source as part of the build process. This took a fair bit of bash wizardry and actually ended up one of the trickiest parts of coding the game (at least in my opinion – Greg’s a lot better at bash than I am).&lt;/p&gt;

&lt;p&gt;Over the next month, Untrusted spread rapidly through word-of-mouth on Twitter, Reddit, and Facebook (in about that order). Fortunately there were no more major outages.&lt;/p&gt;

&lt;h2 id=&quot;and-since-then&quot;&gt;And Since Then&lt;/h2&gt;

&lt;p&gt;If I may brag for a moment: Six months after its release to the world, Untrusted has been played 700,000 times by 380,000 players from 196 countries (top five countries, in order: United States, Russia, China, Poland, France). The &lt;a href=&quot;https://github.com/AlexNisnevich/untrusted&quot;&gt;Untrusted GitHub repo&lt;/a&gt; has been starred nearly 2000 times and forked 400 times. It’s certainly become much more popular than either of us had expected from our half-baked last-minute hackathon idea last year.&lt;/p&gt;

&lt;p&gt;A few articles have been written about us. Some of my favorites are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;our mention in PC Gamer’s &lt;a href=&quot;https://web.archive.org/web/20140824073418/http://www.pcgamer.com/2014/05/30/the-best-free-online-games-on-pc/3/&quot;&gt;list of 100 best free online games&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Rachel Ponce’s review, &lt;a href=&quot;https://web.archive.org/web/20211025141923/http://www.brainsforgames.rachelnponce.com/2014/04/mini-review-untrusted-is-game-more-like.html&quot;&gt;“Untrusted is a game more like real world programming than you might think”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://web.archive.org/web/20150121084716/http://www.killbot86.com/games/new-doctor-who-game-teaches-kids-code-untrusted-teaches-adults-hack/&quot;&gt;KillBot 86’s review&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://web.archive.org/web/20180831220857/http://gameswithpurpose.org/untrusted/&quot;&gt;Games with Purpose’s review&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.gamesidestory.com/2014/09/05/gametest-untrusted-navigateur/&quot;&gt;GameSideStory’s review&lt;/a&gt; (in French)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What excites us the most is all the different ways we’ve seen Untrusted used. For example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The Tech Club at &lt;a href=&quot;https://twitter.com/vianneygriffins&quot;&gt;Vianney Griffiths High School&lt;/a&gt; in St. Louis has &lt;a href=&quot;https://twitter.com/drewmca/status/454721438348361729/photo/1&quot;&gt;used Untrusted to teach basic programming concepts&lt;/a&gt;. Greg and I ended up videochatting with the students of the Tech Club and sharing our experiences with programming.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/CoderScot&quot;&gt;CoderDojo Scotland&lt;/a&gt; has also been &lt;a href=&quot;https://twitter.com/CoderScot/status/462247784419430400&quot;&gt;using Untrusted as an educational tool&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/janosgyerik&quot;&gt;Janos Gyerik&lt;/a&gt; forked Untrusted and began constructing a new level set called &lt;a href=&quot;http://janosgyerik.github.io/hangoverx/&quot;&gt;HangoverX&lt;/a&gt;, including some fiendishly clever puzzles.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/mikespook&quot;&gt;mikespook&lt;/a&gt; has created a &lt;a href=&quot;https://github.com/mikespook/untrusted&quot;&gt;Chinese localization&lt;/a&gt; of Untrusted, as well as adding mod support to the game and beginning to work on a new level set.&lt;/li&gt;
  &lt;li&gt;A few companies have reached out to us about using modified Untrusted level sets for interviews.&lt;/li&gt;
  &lt;li&gt;Someone even made an &lt;a href=&quot;http://www.reddit.com/r/untrusted&quot;&gt;Untrusted subreddit&lt;/a&gt;, though it was only active for a few days.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All in all, we’ve been completely overwhelmed by the community that’s developed around our game. Thanks for all your support, guys :-)&lt;/p&gt;

&lt;h2 id=&quot;whats-next-for-untrusted&quot;&gt;What’s Next for Untrusted?&lt;/h2&gt;

&lt;p&gt;There are some exciting applications of Untrusted in the pipeline. A group in Colombia has been working on making a Spanish-language version of Untrusted designed to serve as an introduction to programming, and we’ve also heard of a similar development effort taking place in India. We’re pretty excited to see what will happen with these. To tell the truth, we had never actually intended Untrusted to be an educational game – our goal was just to make a fun little game for programmers. But seeing all of these different people take our silly game and use it to help people get into programming has been incredible.&lt;/p&gt;

&lt;p&gt;As for the game itself, we’re no longer continually working on it, but we are still maintaining it, fixing bugs as they pop up and looking through pull requests we’ve received. Our main goal now is the make the game more community-driven – to this end, the last major feature we added to Untrusted was &lt;a href=&quot;https://github.com/AlexNisnevich/untrusted#contributing-levels&quot;&gt;custom level support&lt;/a&gt; (player-submitted bonus levels become available from the main menu after when you get to the last level of the game). We haven’t gotten as many level submissions as we’d expected so far, but that’s our fault for adding this feature long after the main waves of players have died down.&lt;/p&gt;

&lt;p&gt;Greg and I both learned a great deal from creating, releasing, and maintaining Untrusted, and we’ll certainly keep these lessons in mind as we work on our future projects, whatever they may be.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Setting up a Flask application on EC2 Ubuntu with Apache + mod_wsgi</title>
   
    <link href="http://alex.nisnevich.com/blog/2014/10/01/setting_up_flask_on_ec2.html"/>
   
   <updated>2014-10-01T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2014/10/01/setting_up_flask_on_ec2</id>
   
    <content type="html">&lt;p&gt;Last weekend I had to restart &lt;a href=&quot;http://melodypy.com&quot;&gt;my melody.py live demo&lt;/a&gt;, as the EC2 instance it was on was outdated. In the process I had to figure out again how to set up &lt;a href=&quot;http://flask.pocoo.org/&quot;&gt;Flask&lt;/a&gt; applications using &lt;a href=&quot;http://en.wikipedia.org/wiki/Mod_wsgi&quot;&gt;mod_wsgi&lt;/a&gt;. As a relative newbie to both EC2 and Apache, the process took longer than I’d like, so I’m documenting the steps I took in detail, in part to make it easier for me next time I need to do something like this.&lt;/p&gt;

&lt;p&gt;I’m going to break the process up into three bite-sized chunks:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Launching an EC2 instance&lt;/li&gt;
  &lt;li&gt;Setting up Flask and Apache&lt;/li&gt;
  &lt;li&gt;Configuring mod_wsgi&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h1&gt;
&lt;p&gt;I assume you have a working Flask app that has a remote git repo, perhaps on GitHub.&lt;/p&gt;

&lt;h1 id=&quot;launching-an-ec2-instance&quot;&gt;Launching an EC2 Instance&lt;/h1&gt;

&lt;p&gt;First things first, let’s set up an EC2 instance running Ubuntu. I’m going to defer to &lt;a href=&quot;http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-launch-instance_linux.html&quot;&gt;Amazon’s documentation&lt;/a&gt; here, which is far more complete than anything I could write here.&lt;/p&gt;

&lt;p&gt;A few things to keep in mind:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Make sure that the security group you assign your instance to allows HTTP access (port 80) from everywhere (source 0.0.0.0/0) and allows SSH access (port 22) from your local machine’s IP.&lt;/li&gt;
  &lt;li&gt;Make sure that your instance is assigned a Public IP (this setting may be unchecked by default).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the instance is created, let’s make sure that we can SSH into it:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;ssh &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; ~/.ssh/&amp;lt;path to .pem file&amp;gt; ubuntu@&amp;lt;public DNS address&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If SSH complains about your private key file being insecure, chmod it to 0600 and try again:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo chmod &lt;/span&gt;0600 &amp;lt;path to .pem file&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;setting-up-flask-and-apache&quot;&gt;Setting up Flask and Apache&lt;/h1&gt;

&lt;p&gt;Now that the EC2 instance is up and running and we can SSH into it, let’s install Apache, mod_wsgi, Flask, and git:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;apache2 apache2-base apache2-mpm-prefork apache2-utils libexpat1 ssl-cert
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;libapache2-mod-wsgi python-pip git
pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;flask&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;and restart Apache to complete the installation of mod_wsgi:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;service apache2 restart&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;For more information on this step, see &lt;a href=&quot;https://www.digitalocean.com/community/tutorials/installing-mod_wsgi-on-ubuntu-12-04&quot;&gt;the DigitalOcean tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;configuration&quot;&gt;Configuration&lt;/h1&gt;
&lt;p&gt;First things first, we need to make a WSGI file for our application. This is the file that tells Python how to communicate with a web server. I use a very barebones WSGI file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;sys&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'/var/www/{appname}'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;variable&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;py&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;application&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;(for more information about this file, see &lt;a href=&quot;http://flask.pocoo.org/docs/0.10/deploying/mod_wsgi/&quot;&gt;the Flask mod_wsgi documentation&lt;/a&gt;). For convenience, I like to store this file directly in git, so save it as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[appname].wsgi&lt;/code&gt; in our app root directory and add it to our app’s git repo.&lt;/p&gt;

&lt;p&gt;Now, let’s deploy this application to the server:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /var/www/
git clone &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;path to git repo] &lt;span class=&quot;c&quot;&gt;# you may need to mess with permissions on the /var/www/ directory&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, we just need to configure Apache to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod_wsgi&lt;/code&gt; and point to our WSGI file.&lt;/p&gt;

&lt;p&gt;Go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/apache2/sites-available/&lt;/code&gt; and make a new file – let’s call it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;amazonaws.com.conf&lt;/code&gt;. This is the file that will tell Apache what to do when someone visits our site. A basic template that works for me is:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;NameVirtualHost &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;:80

&amp;lt;VirtualHost &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;:80&amp;gt;
        ServerName &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;our EC2 server&lt;span class=&quot;s1&quot;&gt;'s public DNS domain name]
        WSGIScriptAlias / /var/www/[appname]/[appname].wsgi
        &amp;lt;Directory /var/www/[appname]/&amp;gt;
                Order allow,deny
                Allow from all
        &amp;lt;/Directory&amp;gt;
        ErrorLog ${APACHE_LOG_DIR}/error.log
        LogLevel info
        CustomLog ${APACHE_LOG_DIR}/access.log combined
&amp;lt;/VirtualHost&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now let’s enable the site we just configured:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;a2ensite amazonaws.com
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;a2dissite 000-default  &lt;span class=&quot;c&quot;&gt;# and for good measure, let's disable the default placeholder site&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Almost done! Now we just need to restart apache:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo&lt;/span&gt; /etc/init.d/apache2 reload&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;is what most tutorials suggest, but for some reason I kept seeing the default Apache loading page after this.
Without digging too deeply, I solved this problem by doing:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apachectl restart&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;instead.&lt;/p&gt;

&lt;p&gt;For more information on this step, see &lt;a href=&quot;https://www.digitalocean.com/community/tutorials/using-mod_wsgi-to-serve-applications-on-ubuntu-12-04&quot;&gt;this DigitalRiver tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you’re made it this far, hopefully your Flask application is running correctly on EC2.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Postmortem - Shattered Worlds</title>
   
    <link href="http://alex.nisnevich.com/blog/2014/09/27/shattered_worlds_postmortem.html"/>
   
   <updated>2014-09-27T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2014/09/27/shattered_worlds_postmortem</id>
   
    <content type="html">&lt;p style=&quot;text-align: center;&quot;&gt;[&lt;a href=&quot;https://web.archive.org/web/20160818214946/http://www.ludumdare.com/compo/2014/09/11/shattered-worlds-postmortem/&quot;&gt;Original post on ludumdare.com&lt;/a&gt;]&lt;/p&gt;

&lt;p style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20160818212600/http://ludumdare.com/compo/ludum-dare-30/?action=preview&amp;uid=3353&quot;&gt;&lt;img class=&quot;aligncenter&quot; alt=&quot;&quot; src=&quot;https://web.archive.org/web/20160818212600im_/http://ludumdare.com/compo/wp-content/compo2//375043/3353-shot0.png-eq-900-500.jpg&quot; width=&quot;675&quot; height=&quot;360&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p style=&quot;text-align: left;&quot;&gt;True to my promise in &lt;a href=&quot;http://www.ludumdare.com/compo/2014/05/07/asteroid-tycoon-postmortem/&quot;&gt;my last postmortem&lt;/a&gt;, I worked in a team again for LD#30. We had a great mix of veterans (Alex, Greg, Natasha, Tikhon doing coding, and Jordan doing art and design) and newcomers (Beth doing level design and writing, and Aylan doing music and SFX), and we’re pretty proud with our end result: &lt;a href=&quot;http://www.ludumdare.com/compo/ludum-dare-30/?action=preview&amp;uid=3353&quot;&gt;Shattered Worlds&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s talk about what we did right and wrong. But words are boring, so let’s illustrate this postmortem with a selection of some of the &lt;a href=&quot;https://github.com/mroth/lolcommits&quot;&gt;lolcommits&lt;/a&gt; that some of us took during the jam.&lt;/p&gt;

&lt;h2&gt;What went right&lt;/h2&gt;
&lt;strong&gt;Simplicity over complexity&lt;/strong&gt;

&lt;p&gt;Unlike &lt;a href=&quot;http://www.ludumdare.com/compo/2014/05/07/asteroid-tycoon-postmortem/&quot;&gt;last time&lt;/a&gt;‘s brainstorming session, where we tried to combine many ideas together into a very complex game, this time around we tried to distill our ideas down into a core gameplay element. We tried a few ideas that involved manipulating properties of different worlds before finally settling on the mechanic of overlaying levels together.&lt;/p&gt;

&lt;p style=&quot;text-align: center;&quot;&gt;&lt;img class=&quot;aligncenter&quot; src=&quot;/blog/images/ld30-lolcommits/4eaa8b3b909.jpg&quot; width=&quot;280&quot; height=&quot;210&quot;&gt;&lt;/p&gt;

&lt;strong&gt;A mechanic that gave us a lot to work with&lt;/strong&gt;

&lt;p&gt;At the same time, we were fortunate to not be constricted too much by our gameplay mechanic. We were able to come up with a wide variety of settings and obstacles to go with it, from bees to bears to lasers.&lt;/p&gt;

&lt;p style=&quot;text-align: center;&quot;&gt;&lt;img class=&quot;aligncenter&quot; src=&quot;/blog/images/ld30-lolcommits/f123088f27d.jpg&quot; width=&quot;280&quot; height=&quot;210&quot;&gt;&lt;/p&gt;

&lt;strong&gt;Good team composition and task management&lt;/strong&gt;

&lt;p&gt;Having a team of people with diverse talents came in handy once again, as we were able to split up the work pretty well. Compared to last time, we had one more designer and a few fewer programmers. This seemed like a pretty positive change: two designers meant that a lot of thought went into every aspect of level design and also that Jordan was free to focus more time on the art assets, while we had just enough programmers that we all knew what we were doing and weren’t stepping on each others’ toes too much.&lt;/p&gt;

&lt;p style=&quot;text-align: center;&quot;&gt;&lt;img class=&quot;aligncenter&quot; src=&quot;/blog/images/ld30-lolcommits/7794fec672f.jpg&quot; width=&quot;280&quot; height=&quot;210&quot;&gt;&lt;/p&gt;

&lt;strong&gt;Distinctive style and flavor&lt;/strong&gt;

&lt;p&gt;We tried hard to make a game that looked and felt unique, and I feel that we did a pretty good job in that regard. Giving each world type a completely different palette and style helped (initially we were hoping to have distinctive music for each world too, but sadly our musician didn’t have quite enough time to make that happen), as did the quirky in-game messages. There are certainly places where we could have used more polish, but overall I think we didn’t do too bad in the style department.&lt;/p&gt;

&lt;p style=&quot;text-align: center;&quot;&gt;&lt;img class=&quot;aligncenter&quot; src=&quot;/blog/images/ld30-lolcommits/2cd46efd02b.jpg&quot; width=&quot;280&quot; height=&quot;210&quot;&gt;&lt;/p&gt;

&lt;strong&gt;Hint system&lt;/strong&gt;

&lt;p&gt;We always think our games are easier to figure out than they actually are, simply by virtue of playing them over and over while making them. In light of this, Tikhon had the foresight to propose a hint system to guide players when they needed help. Many of us initially thought that hints would be unnecessary and would make the game too easy, but we have since seen the error of our ways: quite a few comments specifically mentioned the hints as an important component of the game.&lt;/p&gt;

&lt;p style=&quot;text-align: center;&quot;&gt;&lt;img class=&quot;aligncenter&quot; src=&quot;/blog/images/ld30-lolcommits/10600636_789688137760490_3532397712478429928_n.jpg&quot; width=&quot;280&quot; height=&quot;210&quot;&gt;&lt;/p&gt;

&lt;h2&gt;What went wrong&lt;/h2&gt;
&lt;strong&gt;Switching frameworks in the middle of it all&lt;/strong&gt;

&lt;p&gt;For the first day of coding, we used &lt;a href=&quot;http://www.createjs.com/#!/EaselJS&quot;&gt;EaselJS&lt;/a&gt;. Easel is ok, but none of us knew it particularly well, and it didn’t seem to have anything built-in that would help us make a platformer (all in all, we essentially used Easel as a wrapper for canvas). Our code quickly became a tangled mess of modified example code we found online and our own attempts at doing things like collision detection. It was clear that we couldn’t really go on like this.&lt;/p&gt;

&lt;p&gt;The solution was &lt;a href=&quot;http://wellcaffeinated.net/PhysicsJS/&quot;&gt;PhysicsJS&lt;/a&gt;, an awesome physics simulation library that had all the tools we needed to cleanly and efficiently create the physics for our game. Unfortunately, porting our code over from Easel to PhysicsJS was not as easy as we had hoped, because the two libraries represent objects and their interactions in completely different and incompatible ways. Come Saturday evening, Tikhon began to spearhead the effort to switch to PhysicsJS while the rest of us continued working in Easel, but by Sunday we realized that the only way PhysicsJS would work for us would be if we essentially did a complete rewrite.&lt;/p&gt;

&lt;p&gt;This was a huge decision. Basically all the code that we wrote on Saturday and early Sunday had to be scrapped, and we had to build up the game almost from scratch. As Sunday came to an end, we just barely got a couple of levels working. The switch to PhysicsJS was complete, but it left us with less than a day left to implement most of the levels. Somehow, we still managed to finish. I still think the decision that we made was the correct one — our old physics were horribly buggy and PhysicsJS enabled us to do a lot of cool things that we hadn’t even considered at first (like movable crates in the zombie level, which we got essentially by accident). If we’d stuck with Easel instead, our final game may have had a few more levels in the end, but it wouldn’t play nearly as well. Of course, the best thing we could have done in hindsight would have been to actually research our framework / library options ahead of time rather than &gt;24 hours in.&lt;/p&gt;

&lt;p style=&quot;text-align: center;&quot;&gt;&lt;img class=&quot;aligncenter&quot; src=&quot;/blog/images/ld30-lolcommits/10348289_789688051093832_9122273071838421218_n.jpg&quot; width=&quot;280&quot; height=&quot;210&quot;&gt;&lt;/p&gt;

&lt;strong&gt;Balancing was pretty hard&lt;/strong&gt;

&lt;p&gt;In particular, the low-gravity space levels — while very cool conceptually — proved to be an enormous pain to plan for during level design, because for every level after the first space level, we had to consider the possibility of the player activating low-gravity and try to make the level non-trivial to beat. We definitely didn’t do the best possible job here: some levels are still much too easy with the use of low-gravity.&lt;/p&gt;

&lt;p&gt;If we’d had a little more time, we had plans for a system where some levels would be disabled from overlaying other levels, based on game objects not being able to overlap one another. This feature would have conceivably enabled us to make more levels without having to necessarily consider every possible pair of overlapping levels.&lt;/p&gt;

&lt;p style=&quot;text-align: center;&quot;&gt;&lt;img class=&quot;aligncenter&quot; src=&quot;/blog/images/ld30-lolcommits/b8580c6c5a9.jpg&quot; width=&quot;280&quot; height=&quot;210&quot;&gt;&lt;/p&gt;

&lt;strong&gt;We couldn’t get the controls quite right&lt;/strong&gt;

&lt;p&gt;We spent some time making the game feel right as a platformer, and even had some of our platformer-loving friends playtest it from time to time, but we never got the feel quite right, not even within PhysicsJS. Fortunately, this doesn’t seem to bother too many people, though aforementioned platformer-loving friends were a bit unhappy with it.&lt;/p&gt;

&lt;p style=&quot;text-align: center;&quot;&gt;&lt;img class=&quot;aligncenter&quot; src=&quot;/blog/images/ld30-lolcommits/9d40db32a36.jpg&quot; width=&quot;280&quot; height=&quot;210&quot;&gt;&lt;/p&gt;

&lt;strong&gt;Not much of an ending&lt;/strong&gt;

&lt;p&gt;You go through 11 levels accompassing four different worlds and fight through everything from space battles to a zombie siege, and when you reach the end you get … a dialog essentially saying “The End”. It would have been great to have a real ending of some kind, but by the last hour of the jam we unfortunately still had our hands full just making the rest of the game work. Alas.&lt;/p&gt;

&lt;p style=&quot;text-align: center;&quot;&gt;&lt;img class=&quot;aligncenter&quot; src=&quot;/blog/images/ld30-lolcommits/10647105_789688724427098_8637058848905680658_n.jpg&quot; width=&quot;280&quot; height=&quot;210&quot;&gt;&lt;/p&gt;

&lt;h2&gt;All in all&lt;/h2&gt;
&lt;p&gt;We’re pretty happy with how Shattered Worlds turned out, and LD#30 was a great experience overall. It seems to me that we’re improving as game creators and as a team (well, it’s not exactly the same team, but it’s close enough), though we’ll have to wait for judging to end to see how true that is. Hopefully we can make participating in the LD Jam a regular thing. :-)&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>A First Foray into Experiential Art</title>
   
    <link href="http://alex.nisnevich.com/blog/2014/06/27/my_first_foray_into_experiential_art.html"/>
   
   <updated>2014-06-27T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2014/06/27/my_first_foray_into_experiential_art</id>
   
    <content type="html">&lt;p&gt;I presented “A Snack in Every Port” at &lt;a href=&quot;https://web.archive.org/web/20170616091447/http://www.appetiteobscure.org:80/potlucks/no-8/&quot;&gt;Appetite Obscure #8: Seacret&lt;/a&gt;,
an interactive potluck.&lt;/p&gt;

&lt;p&gt;From the description on the site:&lt;/p&gt;

&lt;div class=&quot;quote&quot;&gt;
	&lt;h3 class=&quot;appetiteObscureTitle&quot;&gt;A Snack in Every Port&lt;/h3&gt;

	&lt;img class=&quot;figure&quot; src=&quot;/blog/images/a-snack-in-every-port.png&quot; width=&quot;500px&quot; /&gt;

	&lt;p&gt;A map and a compass lie in front of you. Look for the dot that represents your ship. Place the point of the compass on the dot, and mark the ship’s next location with the pencil. Where you land determines what you eat.&lt;/p&gt;

	&lt;p&gt;
		Open Water: Crust of Bread &amp;amp; Slice of Lime&lt;br /&gt;
		Cookie Confederation: Sugar Cookie&lt;br /&gt;
		Appetizer Archipelago: Mini Shishkabob&lt;br /&gt;
		Cupcake Coast: Chocolate Cupcake&lt;br /&gt;
		Sultanate of Salmon: Cracker with Salmon and Cream Cheese&lt;br /&gt;
		Principality of Parch: Dangerous-looking Bottle of Unidentified Booze
	&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Appetite Obscure was great fun altogether (from preparing the dish to demonstrating it to trying other dishes), and I look forward to attending more interactive potlucks in the future.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Postmortem - Asteroid Tycoon</title>
   
    <link href="http://alex.nisnevich.com/blog/2014/05/07/asteroid_tycoon_postmortem.html"/>
   
   <updated>2014-05-07T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2014/05/07/asteroid_tycoon_postmortem</id>
   
    <content type="html">&lt;p style=&quot;text-align: center;&quot;&gt;[&lt;a href=&quot;https://web.archive.org/web/20160818214932/http://ludumdare.com/compo/2014/05/07/asteroid-tycoon-postmortem/&quot;&gt;Original post on ludumdare.com&lt;/a&gt;]&lt;/p&gt;

&lt;p style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20160818212535/http://ludumdare.com/compo/ludum-dare-29/?action=preview&amp;uid=3353&quot;&gt;&lt;img class=&quot;aligncenter&quot; alt=&quot;&quot; src=&quot;https://web.archive.org/web/20160818212535im_/http://ludumdare.com/compo/wp-content/compo2//342546/3353-shot0.png-eq-900-500.jpg&quot; width=&quot;675&quot; height=&quot;425&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;For this Ludum Dare, I worked with a group of friends to create &lt;a href=&quot;https://web.archive.org/web/20160818212535/http://ludumdare.com/compo/ludum-dare-29/?action=preview&amp;uid=3353&quot;&gt;Asteroid Tycoon&lt;/a&gt;. I’ve done the Compo a few times in the past, but none of us had ever game jammed in a team before, and it was quite the experience. I’ve learned that doing Ludum Dare with a team brings with it both enormous benefits and unexpected challenges.&lt;/p&gt;

&lt;h2&gt;What went right&lt;/h2&gt;
&lt;strong&gt;Diverse talent&lt;/strong&gt;

&lt;p&gt;Being able to work with an artist, a UI designer, a game designer, an amateur musician, etc (some people filled multiple of these roles) was an amazing experience compared to previous Ludum Dares. For the first time, I’ve submitted a game that actually looks and feels really solid.&lt;/p&gt;

&lt;strong&gt;Combining multiple ideas together&lt;/strong&gt;

&lt;p&gt;During our team brainstorming session, a few ideas became big hits: a candy-box-like about building an asteroid mining empire, something involving moles and tunneling, and a game with programmable robots. The idea for Asteroid Tycoon came about as we realized that we could combine elements from these three ideas into a coherent concept.&lt;/p&gt;

&lt;strong&gt;Constant balance tweaking&lt;/strong&gt;

&lt;p&gt;Gameplay elements underwent many changes as work on the game went on. For example, the robot upgrade progression initially was triggered by amount of minerals collected (a different mineral for each robot), but switching to a depth-based system proved to be a lot more enjoyable for the player. For much of the weekend (at least once we had something playable), at least one person (usually a team member taking a break) was playing the game at all times, so we constantly had feedback that we used to adjust parameters, and in many cases, rework entire mechanics.&lt;/p&gt;

&lt;strong&gt;Cute little touches&lt;/strong&gt;

&lt;p&gt;Little things like the marquee seem to have made a big difference in enjoyment and immersion. Also, some of our last-minute graphical tweaks, such as the beaming-down animation from the ship, improved the look of the game rather significantly.&lt;/p&gt;
&lt;h2&gt;What went wrong&lt;/h2&gt;
&lt;strong&gt;Repetitive music&lt;/strong&gt;

&lt;p&gt;Once we had a short loop of music, we deemed that “good enough for now” and decided to come back to the music situation if we had time. Unfortunately, we didn’t have any more time to work on music, and the result was slightly disastrous, as the constantly repeating main loop proved to be annoying to many players. We quickly added a mute button, but the damage was already done. In the future, I’d hesitate to add music unless it was sufficiently varied to not be a hindrance.&lt;/p&gt;

&lt;strong&gt;Not enough organization&lt;/strong&gt;

&lt;p&gt;Never having worked with a team on Ludum Dare before meant that my default workflow was an informal, poorly defined task list and a single development branch in git. This is reasonable enough for one person, but proved to be a disaster when working with a team of 7 people. On several occasions, lack of communication led to two programmers separately implementing the exact same feature or our two artists stepping on each others’ toes. Meanwhile, all work being on a single branch meant that nearly every commit resulted in a merge conflict, which proved to be very time-consuming in the long run. In retrospect, we should have had a more well-defined task-assignment scheme and used feature branches.&lt;/p&gt;

&lt;strong&gt;Gameplay not explained clearly enough&lt;/strong&gt;

&lt;p&gt;The primary gameplay mechanic in Asteroid Tycoon works as follows: you first click on a robot to build and then click on a destination square for it to try to move to. However, we didn’t explain this very clearly within the game itself, which led to many players thinking that they had to click on the surface or on the spaceship — the result being that the robots reached their destination immediately and from then on proceeded to move in a way that would have appeared to be more or less random to the players.&lt;/p&gt;

&lt;p&gt;To make matters worse, important information is conveyed to the player via printouts that regularly appear but go away on the next mouse-click. What we didn’t anticipate was that most players click around so quickly that they don’t even get a chance to see the printout before accidentally closing it (old printouts are still saved in the top-left panel, but most players never bothered to use it).&lt;/p&gt;

&lt;p&gt;In retrospect, we should have done a better job of explaining exactly how gameplay works from the start, and come up with a different way to hide printouts (perhaps by giving them a Close button). These are both hopefully things that we will work on in the post-Jam version.&lt;/p&gt;

&lt;strong&gt;Only starting work after 18 hours&lt;/strong&gt;

&lt;p&gt;We made the conscious decision to not start work until noon (PST) Saturday, to give our brains plenty of time to process our different ideas. While this gave us lots of brainstorming time, it put us at a disadvantage time-wise compared to most Jam teams, and led to us being very pressed for time near the end (compounded by the fact that many of us had to go to work on Monday). In the future, I’d like to spend a little less time brainstorming and perhaps at least try to get a little bit of actual coding done Friday night.&lt;/p&gt;
&lt;h2&gt;All in all&lt;/h2&gt;
&lt;p&gt;After trying a Ludum Dare with a team for the first time, I don’t know if I can go back to doing the Compo! Not only were we able to build something bigger and more polished than any one of us could do on their own, but we had lots of fun working as a group. We’ll try to work together in future Ludum Dares.&lt;/p&gt;

&lt;p&gt;As far as Asteroid Tycoon goes, we’re really happy with how it turned out. We’ll probably tweak it a little bit post-Jam — primarily to make the instructions more clear, make the music a little less repetitive, and fix some sneaky bugs that people have reported.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Postmortem - 10 Second Roguelike</title>
   
    <link href="http://alex.nisnevich.com/blog/2013/09/03/10_second_roguelike_postmortem.html"/>
   
   <updated>2013-09-03T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2013/09/03/10_second_roguelike_postmortem</id>
   
    <content type="html">&lt;p style=&quot;text-align: center;&quot;&gt;[&lt;a href=&quot;https://web.archive.org/web/20160818212509/http://www.ludumdare.com/compo/2013/08/30/10-second-roguelike-postmortem&quot;&gt;Original post on ludumdare.com&lt;/a&gt;]&lt;/p&gt;

&lt;p style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20160818212509/http://www.ludumdare.com/compo/ludum-dare-27/?action=preview&amp;uid=3353&quot;&gt;&lt;img class=&quot;aligncenter&quot; alt=&quot;&quot; src=&quot;https://web.archive.org/web/20160818212509im_/http://ludumdare.com/compo/wp-content/compo2//273708/3353-shot0.png-eq-900-500.jpg&quot; width=&quot;661&quot; height=&quot;307&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;Well, I've never done one of these before, so here goes nothing.&lt;/p&gt;

&lt;h2&gt;What went right&lt;/h2&gt;
&lt;strong&gt;Long brainstorming session&lt;/strong&gt;

&lt;p&gt;I was fortunate in having a group of friends help me brainstorm ideas when the theme was first announced. About a dozen ideas were thrown around, some interesting and some worthless, until I finally settled on this one. My first few ideas were tempting but also ultimately crappy, so being able to bounce back ideas with other people saved me from falling into the usual trap of doing the first thing that fell into my head.&lt;/p&gt;

&lt;strong&gt;Playing to my strengths&lt;/strong&gt;

&lt;p&gt;This isn't the &lt;a href=&quot;http://alex.nisnevich.com/untrusted&quot;&gt;first roguelikelike&lt;/a&gt; I've made, and for good reason, aside from loving the genre: I'm not any good at art, at all. In almost any other genre, I'd be forced to spend long hours putting together terrible art, but fortunately this wasn't an issue for me here.&lt;/p&gt;

&lt;strong&gt;Not reinventing the wheel&lt;/strong&gt;

&lt;p&gt;For level generation and field-of-view computation, I used &lt;a href=&quot;http://ondras.github.io/rot.js/hp/&quot;&gt;rot.js&lt;/a&gt;, an excellent set of utilities specifically designed for roguelikes. I probably could have managed without rot.js, but when you only have 48 hours, why not limit the work you have to do as much as possible? Using rot.js also offered the huge advantage of giving me something vaguely playable within an hour or two of starting, meaning that I could spend the rest of the competition working on making gameplay as fun as possible.&lt;/p&gt;

&lt;strong&gt;Addictive gameplay&lt;/strong&gt;

&lt;p&gt;My main objective with this game was making it as fun and as fast-paced as possible. To this end, I made death be completely harmless, let players immediately create a new character in one click and jump back in the game, and provided a silly soundtrack to accompany the dungeon romp. When my playtesters (er, friends) started to refuse to give me back the computer, I knew that I had stumbled onto something.&lt;/p&gt;

&lt;strong&gt;A little bit of flair&lt;/strong&gt;

&lt;p&gt;Roguelikes are inherently rather bland visually, so small touches to make the game look good were important for the player experience. Things like smooth lighting, a shiny experience bar, and a memorial to dead characters at the end of the game were all relatively quick to implement and provided a nice distraction from the big-picture stuff for me, while making the game look much more polished. Don't skimp on the small stuff!&lt;/p&gt;
&lt;h2&gt;What went wrong&lt;/h2&gt;
&lt;strong&gt;No good plan for the second day&lt;/strong&gt;

&lt;p&gt;Most of the game features (timer, dungeon exploration, combat, character creation) were completed in the first 24 hours, which made me very optimistic about the next 24 hours. However, while there were a lot of things that I wanted to do (including items, spells, and more interesting monsters), I spent so much time debating about what order to do them in that I wasn't able to work most of these features into the game. In the end, my second day consisted mainly of making levels and creating the boss fight and ending, as well as doing a lot of playtesting and minor tweaking. Next time around, I'd like to budget my time a little more effectively, especially for the second day.&lt;/p&gt;

&lt;strong&gt;Not enough replay value&lt;/strong&gt;

&lt;p&gt;Well, you beat the boss, and then the game is over. Several playtesters told me the game needed to have a way to continue playing after the boss fight, but, given how messy my second day was, I declined to implement this at the time. I did eventually address this issue by adding an Infinite mode to the post-compo version though.&lt;/p&gt;

&lt;strong&gt;Dungeon annoyances&lt;/strong&gt;

&lt;p&gt;I didn't spend enough time making sure that all dungeon stairs are reachable within 10 seconds. Sometimes the stairs aren't, though they should still be reachable if you have a faster character (speed &amp;gt; 1.0). And I'm pretty sure there's a (small) chance of the boss appearing in an unreachable location on the final level, though I haven't personally seen that. Regardless, I should have spent more time ensuring that every game is beatable, regardless of luck of the draw.&lt;/p&gt;

&lt;strong&gt;Not enough lore&lt;/strong&gt;

&lt;p&gt;The game's story consists of two paragraphs of flavor text that I hastily cobbled together as the minutes were counting down. I suppose it's not that big a deal for a game that seems to have more in common with Quake than with Rogue, but it still would have been nice to have an actual story.&lt;/p&gt;
&lt;h2&gt;All in all&lt;/h2&gt;
I've taken part in three Ludum Dares before, but this was by far my most successful one: I made a game that most players found to be fun, which is a feat that I have never really achieved before. Next time around, I will hopefully interpret the theme a little more creatively, and I hope to address my biggest issue from LD27: insufficient planning of secondary game features. Still, I'm pretty happy with Ten Second Roguelike. Let me know what you think!
</content>
   
 </entry>
 
 <entry>
   <title>Rolling Your Own Mechanical Turk Form with ExternalQuestion and Rails</title>
   
    <link href="http://alex.nisnevich.com/blog/2013/05/23/rolling_your_own_mechanical_turk_form.html"/>
   
   <updated>2013-05-23T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2013/05/23/rolling_your_own_mechanical_turk_form</id>
   
    <content type="html">&lt;h3 id=&quot;motivation&quot;&gt;Motivation&lt;/h3&gt;

&lt;p&gt;For a research project that I’m working on, I made a survey that feeds data into a Rails application. I wanted to create Mechanical Turk HITs that direct users to the survey (on my site), and send the results directly to the server. I also wanted to be able to dynamically create the HITs from within the Rails application itself.&lt;/p&gt;

&lt;p&gt;Fortunately, the Mechanical Turk API provides a means for doing this through the &lt;a href=&quot;http://docs.aws.amazon.com/AWSMechTurk/latest/AWSMturkAPI/ApiReference_ExternalQuestionArticle.html&quot;&gt;ExternalQuestion structure&lt;/a&gt;. Here’s how I accomplished this.&lt;/p&gt;

&lt;h3 id=&quot;creating-the-hit&quot;&gt;Creating the HIT&lt;/h3&gt;

&lt;p&gt;To create the HIT from within my application, I used the &lt;a href=&quot;https://rubygems.org/gems/ruby-aws&quot;&gt;ruby-aws&lt;/a&gt; gem. Creating HITs with it is pretty simple:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'ruby-aws'&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# For production:&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# mturk = Amazon::WebServices::MechanicalTurkRequester.new :Host =&amp;gt; :Production&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# For testing:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mturk&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Amazon&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;WebServices&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MechanicalTurkRequester&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:Host&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:Sandbox&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;mturk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;createHIT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;:Title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TITLE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;:Description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DESCRIPTION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;:MaxAssignments&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NUM_ASSIGNMENTS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;:Reward&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:Amount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;REWARD_AMOUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:CurrencyCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'USD'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;:Question&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;QUESTION_PATH&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;:Keywords&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;KEYWORDS&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Most of these parameters should be fairly straightforward. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;QUESTION_PATH&lt;/code&gt; is the relative path to a question XML file (I keep mine under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/app/lib/mechanical_turk_questions/&lt;/code&gt;). For ExternalQuestions, the format of the XML file is very simple (the latest schema URLs can be found &lt;a href=&quot;http://docs.aws.amazon.com/AWSMechTurk/latest/AWSMturkAPI/ApiReference_WsdlLocationArticle.html#the-data-structure-schema-locations&quot;&gt;here&lt;/a&gt; ):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;ExternalQuestion xmlns=&quot;[URL of schema]&quot;&amp;gt;
  &amp;lt;ExternalURL&amp;gt;[path to survey]&amp;lt;/ExternalURL&amp;gt;
  &amp;lt;FrameHeight&amp;gt;[height of frame]&amp;lt;/FrameHeight&amp;gt;
&amp;lt;/ExternalQuestion&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, running the above code should create the desired number of HITs, pointing to the desired path. Of course, before we do that we should probably set up the form itself.&lt;/p&gt;

&lt;h3 id=&quot;setting-up-the-form&quot;&gt;Setting up the Form&lt;/h3&gt;

&lt;p&gt;In order to integrate with Mechanical Turk, the form needs to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;submit results to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://www.mturk.com/mturk/externalSubmit&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;pass on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assignmentId&lt;/code&gt; parameter  (I also pass on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hitId&lt;/code&gt; parameter, just in case)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The controller action should look like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SurveyController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationController&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@assignment_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'assignmentId'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@hit_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'hitId'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# whatever other setup is needed for the survey&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The template of the form should look like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-erb&quot; data-lang=&quot;erb&quot;&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;form_tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://www.mturk.com/mturk/externalSubmit&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:method&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;post&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- the actual survey goes here --&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hidden_field_tag&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'assignmentId'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@assignment_id&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hidden_field_tag&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'hitId'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@hit_id&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;submit_tag&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Submit&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:disabled&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'submitButton'&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To ensure that the survey is fully completed, I made the Submit button be disabed at first, and only enabled it via JavaScript once every field was filled out. This is of course strictly optional.&lt;/p&gt;

&lt;h3 id=&quot;submitting-the-result&quot;&gt;Submitting the Result&lt;/h3&gt;

&lt;p&gt;When a Turker hits the Submit button in the survey, I want two things to happen:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The results should be passed on to the server&lt;/li&gt;
  &lt;li&gt;Amazon should be notified that the Turker completed the survey&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the server side, things are pretty simple - I gave &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SurveyController&lt;/code&gt; a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;submit&lt;/code&gt; action that handles the incoming data:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SurveyController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationController&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;submit&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# process the survey result&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now comes the tricky bit - how do we get the data to both the server and to Amazon?&lt;/p&gt;

&lt;p&gt;At first, I tried submitting the survey to my server first, then processing the data and redirecting Turkers to the Amazon URL. However, this didn’t work - the Turkers all ended up receiving a &lt;strong&gt;“There was a problem submitting your results”&lt;/strong&gt; error:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;/blog/images/problem-submitting-results.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As it turns out, Amazon only seems to allow &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;externalSubmit&lt;/code&gt; requests that originate from the client, not ones that originate on the server or are redirected.&lt;/p&gt;

&lt;p&gt;So, the only way to do what we want to do is to submit the form &lt;em&gt;twice&lt;/em&gt; - once to the server and once to Amazon. This is possible with a bit of jQuery trickery:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;input:submit&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// submit to our server&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ajax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/submit&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;serialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// submit to mechanical turk&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;submit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, when the submit button is clicked, the contents of the form are first serialized and passed to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/submit&lt;/code&gt;, and only after that request is successfully made does the form itself submit to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;externalSubmit&lt;/code&gt; URL.&lt;/p&gt;

&lt;h3 id=&quot;in-conclusion&quot;&gt;In Conclusion&lt;/h3&gt;

&lt;p&gt;Putting these pieces together, we get a pretty nifty workflow:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;HITs can be created from within the application.&lt;/li&gt;
  &lt;li&gt;HITs direct Turkers to the survey within the application.&lt;/li&gt;
  &lt;li&gt;Turkers fill out the survey.&lt;/li&gt;
  &lt;li&gt;The results are submitted to the application.&lt;/li&gt;
  &lt;li&gt;The results are then submitted to Mechanical Turk, to credit the Turkers for their work.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The one step that’s still missing is that each Turker’s submission needs to be manually approved or rejected. I’m currently working on figuring out how to automatically approve/reject work - perhaps I’ll write about it in a later post.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>The Church-Rosser Theorem and the Consistency of Lambda Calculus</title>
   
    <link href="http://alex.nisnevich.com/blog/2013/05/17/consistency_of_lambda_calculus.html"/>
   
   <updated>2013-05-17T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2013/05/17/consistency_of_lambda_calculus</id>
   
    <content type="html">&lt;p&gt;For my &lt;a href=&quot;https://www.dropbox.com/s/u1c6pbi0qwtnjqi/The%20Consistency%20of%20Lambda%20Calculus.pdf&quot;&gt;Computability final paper&lt;/a&gt;, I wrote about the &lt;a href=&quot;http://en.wikipedia.org/wiki/Church%E2%80%93Rosser_theorem&quot;&gt;Church-Rosser Theorem&lt;/a&gt; and the consistency of λ-calculus, as well as introducing the general theory of λ-calculus and showing how arithmetic can be performed within it.&lt;/p&gt;

&lt;h3 id=&quot;what-is-the-church-rosser-theorem&quot;&gt;What is the Church-Rosser Theorem?&lt;/h3&gt;

&lt;p&gt;The main operation of λ-calculus (at least in its standard form) is β-reduction. Loosely speaking, β-reduction means that, given a function that takes an argument &lt;span&gt;\(x\)&lt;/span&gt; and returns &lt;span&gt;\(M\)&lt;/span&gt;, applying this function to some argument &lt;span&gt;\(y\)&lt;/span&gt; yields the same result as substituting &lt;span&gt;\(y\)&lt;/span&gt; for every occurrence of &lt;span&gt;\(x\)&lt;/span&gt; in &lt;span&gt;\(M\)&lt;/span&gt;. In the notation of λ-calculus, we write this as:&lt;/p&gt;

&lt;div&gt;\[
(\lambda x . M) y = M [x := y]
\]&lt;/div&gt;

&lt;p&gt;where &lt;span&gt;\((λx.M)\)&lt;/span&gt; means “the function that returns &lt;span&gt;\(M\)&lt;/span&gt; given &lt;span&gt;\(x\)&lt;/span&gt;” and &lt;span&gt;\(M[x := y]\)&lt;/span&gt; means “the result of substituting &lt;span&gt;\(y\)&lt;/span&gt; for &lt;span&gt;\(x\)&lt;/span&gt; in &lt;span&gt;\(M\)&lt;/span&gt;”.&lt;/p&gt;

&lt;p&gt;A statement &lt;span&gt;\(M\)&lt;/span&gt; can be &lt;em&gt;β-reduced&lt;/em&gt; to &lt;span&gt;\(N\)&lt;/span&gt; if you can apply the above formula some number of times to &lt;span&gt;\(M\)&lt;/span&gt; to turn it into &lt;span&gt;\(N\)&lt;/span&gt;.&lt;/p&gt;

&lt;p&gt;For example, &lt;span&gt;\(((λx.(λy.y))z)z\)&lt;/span&gt; β-reduces to &lt;span&gt;\(z\)&lt;/span&gt;, because&lt;/p&gt;

&lt;div&gt;\[
\Big(\big(\lambda x . (\lambda y . y)\big) z\Big) z = \big((\lambda y . y) [x := z]\big) z = (\lambda y . y) z = y [y := z] = z
\]&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;Church-Rosser Theorem&lt;/strong&gt; for β-reduction states that whenever some expression &lt;span&gt;\(M\)&lt;/span&gt; can be β-reduced to two different expressions &lt;span&gt;\(N\)&lt;/span&gt; and &lt;span&gt;\(P\)&lt;/span&gt;, there exists some expression &lt;span&gt;\(Q\)&lt;/span&gt; that both &lt;span&gt;\(N\)&lt;/span&gt; and &lt;span&gt;\(P\)&lt;/span&gt; can β-reduce to. This property is also called &lt;em&gt;confluence&lt;/em&gt;, and is sometimes referred to as the &lt;em&gt;diamond property&lt;/em&gt;, because the resulting graph of reductions looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;figure&quot; src=&quot;/blog/images/confluence.jpg&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;proving-the-church-rosser-theorem&quot;&gt;Proving the Church-Rosser Theorem&lt;/h3&gt;

&lt;p&gt;In my paper, my proof of the Church-Rosser mainly follows the classic Tait-Martin-Löf proof, which is well-known as one of the simplest proofs of the theorem, relying on nothing more than a basic understanding of the theory of β-reductions.&lt;/p&gt;

&lt;p&gt;The proof has three stages:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;First, I introduce a new notion of reduction, that I call ◊, and show that ◊ satisfies the diamond property. In this section, I follow Barendregt’s exposition of the Tait-Martin-Löf proof.&lt;/li&gt;
  &lt;li&gt;Next, I show that ◊-equivalence is the transitive closure of β-equivalence. In other words, the β-equivalence relation is the minimal superset of the ◊-equivalence relation that satisfies transitivity.&lt;/li&gt;
  &lt;li&gt;Finally, I prove the &lt;strong&gt;Strip Lemma&lt;/strong&gt;, which states that if a notion of reduction satisfies the diamond property, then so does its transitive closure. Thus, since ◊ satisfies the diamond property, so does β. I primarily follow Robert Pollack’s &lt;a href=&quot;http://www.researchgate.net/publication/2588155_Polishing_Up_the_Tait-Martin-Lf_Proof_of_the_Church-Rosser_Theorem&quot;&gt;simplified proof&lt;/a&gt; of the Strip Lemma.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;what-does-this-mean-for-consistency&quot;&gt;What does this mean for consistency?&lt;/h3&gt;

&lt;p&gt;In the final section of my paper, I show that it follows from the Church-Rosser Theorem that every expression in λ has a unique “β-normal form” - that is, a unique form from which the expression cannot be β-reduced any further.&lt;/p&gt;

&lt;p&gt;Then, I show that two terms cannot be equal to each other in λ-calculus if they are not β-equivalent. This is a result that follows directly from the axioms of λ-calculus.&lt;/p&gt;

&lt;p&gt;Putting all of these pieces together, if two terms &lt;span&gt;\(M\)&lt;/span&gt; and &lt;span&gt;\(N\)&lt;/span&gt; do not share the same β-normal form, then the statement &lt;span&gt;\(M = N\)&lt;/span&gt; is not provable in λ-calculus.&lt;/p&gt;

&lt;p&gt;It’s easy to find two terms that do not share a β-normal form - for example, &lt;span&gt;\(I = λx.x\)&lt;/span&gt; and &lt;span&gt;\(K = λx.(λy.x)\)&lt;/span&gt;. Thus, the statement &lt;span&gt;\(I = K\)&lt;/span&gt; is not provable in λ-calculus, and so λ-calculus is a consistent theory, because if λ-calculus were inconsistent, then by definition every (syntactically valid) statement would be provable in it.&lt;/p&gt;

&lt;h3 id=&quot;interested-in-this-sort-of-thing&quot;&gt;Interested in this sort of thing?&lt;/h3&gt;

&lt;p&gt;If this has piqued your interest, check out my paper &lt;a href=&quot;https://www.dropbox.com/s/u1c6pbi0qwtnjqi/The%20Consistency%20of%20Lambda%20Calculus.pdf&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>My Experience with Steam for Linux (64-bit Linux Mint Debian Edition)</title>
   
    <link href="http://alex.nisnevich.com/blog/2013/03/25/steam_on_linux.html"/>
   
   <updated>2013-03-25T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2013/03/25/steam_on_linux</id>
   
    <content type="html">&lt;p&gt;I’ve been successfully using Steam on my 64-bit Linux Mint Debian Edition desktop for the past month, and I thought I should make a small post about how I got it working and how well various games that I’ve tried work. I’ll try to keep this post updated as I try running more games.&lt;/p&gt;

&lt;h3 id=&quot;installing-steam-on-debian&quot;&gt;Installing Steam on Debian&lt;/h3&gt;

&lt;p&gt;Installing Steam on 64-bit Debian is a bit of a pain. After trying and failing to get it to work myself, I followed &lt;a href=&quot;http://aspensmonster.com/2013/01/19/updated-procedures-for-installing-steam-for-linux-beta-on-debian-gnulinux-testingwheezy/&quot;&gt;aspensmonster’s instructions&lt;/a&gt;, which go over everything from setting up multiarch to modifying and rebuilding the steam package to fix the bugs in it. The guide has been updated fairly regularly as new versions of Steam for Linux came out, and should be more or less up-to-date.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://gist.github.com/grindars/4231563&quot;&gt;This Debian install script&lt;/a&gt; may be helpful as well, but I haven’t had a chance to try it.&lt;/p&gt;

&lt;h3 id=&quot;games&quot;&gt;Games&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Worked immediately&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Required modification&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Works, but with issues&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Couldn’t get to work&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;#aquaria&quot;&gt;Aquaria&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;#bastion&quot;&gt;Bastion&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;#dod&quot;&gt;Dungeons of Dredmor&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;#tf2&quot;&gt;Team Fortress 2&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;#closure&quot;&gt;Closure&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;#ftl&quot;&gt;Faster Than Light&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;#frozen&quot;&gt;Frozen Synapse&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;#gsb&quot;&gt;Gratuitous Space Battles&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;#vvvvvv&quot;&gt;VVVVVV&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;#worldofgoo&quot;&gt;World of Goo&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 id=&quot;aquaria&quot;&gt;&lt;a id=&quot;aquaria&quot;&gt;&lt;/a&gt;Aquaria&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Aquaria&lt;/em&gt; works perfectly from the start.&lt;/p&gt;

&lt;h4 id=&quot;bastion&quot;&gt;&lt;a id=&quot;bastion&quot;&gt;&lt;/a&gt;Bastion&lt;/h4&gt;

&lt;p&gt;At first, when launching &lt;em&gt;Bastion&lt;/em&gt; I just got a black screen after the Supergiant logo appeared. This is a &lt;a href=&quot;http://ubuntuforums.org/showthread.php?p=11992661&quot;&gt;known issue with S3tc textures&lt;/a&gt; and I could fix it by opening &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/.steam/root//SteamApps/common/Bastion/Linux/run_steam.sh&lt;/code&gt; (your path may differ) and editing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./Bastion.bin.x86&lt;/code&gt; line to be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;force_s3tc_enable=true ./Bastion.bin.x86&lt;/code&gt;.&lt;/p&gt;

&lt;h4 id=&quot;closure&quot;&gt;&lt;a id=&quot;closure&quot;&gt;&lt;/a&gt;Closure&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Closure&lt;/em&gt; works perfectly from the start.&lt;/p&gt;

&lt;h4 id=&quot;dungeons-of-dredmor&quot;&gt;&lt;a id=&quot;dod&quot;&gt;&lt;/a&gt;Dungeons of Dredmor&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Dungeons of Dredmor&lt;/em&gt; works, but suffers from fairly noticeable lag compared to its performance on Windows. It looks like this is a &lt;a href=&quot;http://community.gaslampgames.com/threads/dod-linux-very-slow.3635/&quot;&gt;known Linux problem&lt;/a&gt;, but I haven’t been able to find a fix yet.&lt;/p&gt;

&lt;h4 id=&quot;faster-than-light&quot;&gt;&lt;a id=&quot;ftl&quot;&gt;&lt;/a&gt;Faster Than Light&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Faster Than Light&lt;/em&gt; crashed on startup at first, due to my open-source video driver. There are &lt;a href=&quot;https://wiki.archlinux.org/index.php/Steam#Problems_with_open-source_video_driver&quot;&gt;two possible solutions&lt;/a&gt;; I opted for the easy way out and simply deleted &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/.steam/root/SteamApps/common/FTL\ Faster\ Than\ Light/data/amd64/lib/libstdc++.so.6&lt;/code&gt; (your path may differ). &lt;em&gt;Faster Than Light&lt;/em&gt; now runs perfectly, and, surprisingly, is actually much faster under Steam Linux than the standalone Linux binary of &lt;em&gt;FTL&lt;/em&gt; is.&lt;/p&gt;

&lt;h4 id=&quot;frozen-synapse&quot;&gt;&lt;a id=&quot;frozen&quot;&gt;&lt;/a&gt;Frozen Synapse&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Frozen Synapse&lt;/em&gt; works perfectly from the start. There seems to be a bit of mouse lag, but it’s hardly noticeable.&lt;/p&gt;

&lt;h4 id=&quot;gratuitous-space-battles&quot;&gt;&lt;a id=&quot;gsb&quot;&gt;&lt;/a&gt;Gratuitous Space Battles&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Gratuitous Space Battles&lt;/em&gt; works perfectly from the start.&lt;/p&gt;

&lt;h4 id=&quot;team-fortress-2&quot;&gt;&lt;a id=&quot;tf2&quot;&gt;&lt;/a&gt;Team Fortress 2&lt;/h4&gt;

&lt;p&gt;I haven’t yet been able to get &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Team Fortress 2&lt;/code&gt; to work. When I try to launch it, I get the error &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Required OpenGL extension &quot;GL_EXT_texture_compression_s3tc&quot; is not supported. Please install S3TC texture support.&quot;&lt;/code&gt;. It looks like &lt;a href=&quot;http://steamcommunity.com/app/221410/discussions/0/864959336401282074/&quot;&gt;others have had this problem&lt;/a&gt;, but none of the fixes that I’ve seen mentioned work for me. However, I’m not an FPS fan, so I haven’t put very much time into trying to get &lt;em&gt;TF2&lt;/em&gt; to work.&lt;/p&gt;

&lt;h4 id=&quot;vvvvvv&quot;&gt;&lt;a id=&quot;vvvvvv&quot;&gt;&lt;/a&gt;VVVVVV&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;VVVVVV&lt;/em&gt; works perfectly from the start.&lt;/p&gt;

&lt;h4 id=&quot;world-of-goo&quot;&gt;&lt;a id=&quot;worldofgoo&quot;&gt;&lt;/a&gt;World of Goo&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;World of Goo&lt;/em&gt; works perfectly from the start.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Face Tracking with OpenCV and a USB Missile Launcher</title>
   
    <link href="http://alex.nisnevich.com/blog/2013/02/19/face_tracking_with_open_cv_and_a_usb_missile_launcher.html"/>
   
   <updated>2013-02-19T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2013/02/19/face_tracking_with_open_cv_and_a_usb_missile_launcher</id>
   
    <content type="html">&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/AlexNisnevich/sentinel&quot;&gt;Check out Sentinel for Linux, Windows, and OS X on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/images/sentinel.png&quot; alt=&quot;Demonstration of Sentinel&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I recently got my roommate a &lt;a href=&quot;http://www.dreamcheeky.com/thunder-missile-launcher&quot;&gt;Dream Cheeky Thunder&lt;/a&gt; USB missile launcher. It was fun to play around with for a bit, but the included software was very limited in its functionality. Just when I was ready to dismiss it as an overpriced toy, my roommate came up with a great idea: could we mount a camera to the turret and make it automatically aim and fire at faces?&lt;/p&gt;

&lt;p&gt;After a couple weeks of experimentation, we came up with &lt;a href=&quot;https://github.com/AlexNisnevich/sentinel&quot;&gt;Sentinel&lt;/a&gt;, a Python script that does just that, making heavy use of the excellent &lt;a href=&quot;http://opencv.org/&quot;&gt;OpenCV&lt;/a&gt; computer vision library. Here’s how we did it.&lt;/p&gt;

&lt;h3 id=&quot;but-first-a-video-demo&quot;&gt;But first, a video demo!&lt;/h3&gt;

&lt;center&gt;&lt;iframe width=&quot;640&quot; height=&quot;480&quot; src=&quot;http://www.youtube.com/embed/L2It-kK0yfM&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;&lt;/center&gt;

&lt;p&gt;&lt;em&gt;Note: The above video was filmed while our computers were rather bogged down, and Sentinel usually runs signficantly faster than that (especially on Windows and OS X, where it can go as fast as 3 iterations per second).&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;step-zero-the-concept&quot;&gt;Step Zero. The concept&lt;/h3&gt;

&lt;p&gt;The main loop of the program is conceptually quite simple:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;camera&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;capture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;face_detected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x_adj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_adj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;camera&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;face_detect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;camera&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;face_detected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;turret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;adjust&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x_adj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_adj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;turret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ready_aim_fire&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;At each iteration, the camera takes a picture, which is then processed to detect any faces. If a face is detected, the turret adjust itself to bring the face closer to the center of the camera’s field of view. If the face is close enough to the center and the turret is armed, it then fires a missile at its target. The camera’s output is also sent to the screen, with an ominous red reticule drawn over a detected face.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: I’m only showing bits of pieces of our source code here, and I’m simplifying it where I can to keep the code chunks relevant. You can see the full source code &lt;a href=&quot;https://github.com/AlexNisnevich/sentinel/blob/master/sentinel.py&quot;&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;step-one-assembling-the-hardware&quot;&gt;Step One. Assembling the hardware&lt;/h3&gt;

&lt;p&gt;We had a small Logitech C270 webcam lying around, so we taped it to the top of the turret, aiming it roughly parallel to the trajectory of the turret’s missiles, as shown in the photo above.&lt;/p&gt;

&lt;p&gt;The webcam’s position about an inch above the turret means that when the camera is pointing toward a target’s face, the turret usually ends up shooting them in the neck or chest, which is a nice side effect, since neither of us wanted to get shot in the eye.&lt;/p&gt;

&lt;h3 id=&quot;step-two-controlling-the-turret&quot;&gt;Step Two. Controlling the turret&lt;/h3&gt;

&lt;p&gt;Controlling USB devices from Python requires the &lt;a href=&quot;https://github.com/walac/pyusb&quot;&gt;PyUSB&lt;/a&gt; library, which in turn needs a low level driver - we used &lt;a href=&quot;http://www.libusb.org/&quot;&gt;libusb&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After that, connecting to a USB device is just a matter of finding its vendor and product IDs. In the case of this missile launcher, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lsusb&lt;/code&gt; reported these IDs to be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2123:1010&lt;/code&gt;. In Linux, you also need to detach the kernel driver if it is active:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LauncherDriver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
   &lt;span class=&quot;c1&quot;&gt;# Low level launcher driver commands
&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# this code mostly taken from https://github.com/nmilford/stormLauncher
&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# with bits from https://github.com/codedance/Retaliation
&lt;/span&gt;   &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dev&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;usb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idVendor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x2123&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;idProduct&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x1010&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dev&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ValueError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Missile launcher not found.'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;platform&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'linux2'&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_kernel_driver_active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
         &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detach_kernel_driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set_configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now that the device is configured, we can send commands to it. Fortunately for us, &lt;a href=&quot;https://github.com/codedance/Retaliation&quot;&gt;others&lt;/a&gt; had already discovered the commands that the device accepts to position its turret, fire missiles, and toggle its built-in LED:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;   &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;turretUp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctrl_transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x09&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,[&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x02&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x02&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;turretDown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctrl_transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x09&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,[&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x02&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;turretLeft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctrl_transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x09&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,[&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x02&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x04&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;turretRight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctrl_transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x09&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,[&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x02&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x08&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;turretStop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctrl_transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x09&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,[&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x02&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;turretFire&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctrl_transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x09&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,[&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x02&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ledOn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctrl_transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x09&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,[&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x03&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ledOff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctrl_transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x09&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,[&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x03&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;step-three-controlling-the-camera&quot;&gt;Step Three. Controlling the camera&lt;/h3&gt;

&lt;p&gt;Now that we’ve set up the rocket launcher, the next step is accessing the webcam to make it take a picture once per loop iteration. We ended up doing this in a few different ways.&lt;/p&gt;

&lt;h4 id=&quot;linux-streamer&quot;&gt;Linux: streamer&lt;/h4&gt;

&lt;p&gt;On Linux, I was excited to find &lt;a href=&quot;http://linux.die.net/man/1/streamer&quot;&gt;streamer&lt;/a&gt;, a fast and simple-to-use photo/video capture tool. Even after we switched to using OpenCV’s photo capture capabilities on Windows, streamer still ended up giving the best performance on Linux.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# captures a single frame - currently a platform-dependent implementation
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;capture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;platform&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'linux2'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# on Linux, use streamer to generate a jpeg, then have OpenCV load it into self.current_frame
&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;img_file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'capture.jpeg'&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;subprocess&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;streamer -q -c /dev/video&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;camera&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; -s &quot;&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image_dimensions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; -b 16 -o &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;img_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stdout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FNULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shell&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_frame&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cv2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;img_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;windows-and-os-x-opencv&quot;&gt;Windows and OS X: OpenCV&lt;/h4&gt;

&lt;p&gt;Capturing photos on Windows proved trickier. Initially, we used &lt;a href=&quot;https://github.com/tedburke/CommandCam&quot;&gt;CommandCam&lt;/a&gt;, which gave good results but was a litle too slow to use. We finally switched to using OpenCV’s &lt;a href=&quot;http://opencv.willowgarage.com/documentation/reading_and_writing_images_and_video.html&quot;&gt;image capture&lt;/a&gt; methods, but ran into a serious problem.&lt;/p&gt;

&lt;p&gt;The images that OpenCV was processing were always a few frames out of date compared to the images the camera was taking, which ended up making the turret oscillate back and forth instead of homing in to its target, because it was adjusting its position based on outdated information.&lt;/p&gt;

&lt;p&gt;This behavior is actually intentional in OpenCV: images are stored in a buffer and retrieved as quickly as possible, and the latest image taken is not generally the image retrieved. Since OpenCV is generally used in a situation where continuous footage is being taken from a video camera, this is not usually a problem. However, since we were taking only one picture at a time and then repositioning the turret based on each picture, this behavior was unacceptable.&lt;/p&gt;

&lt;p&gt;Our solution was a little hackish but succeeded in correcting the problem: we simply made a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clear_buffer&lt;/code&gt; method that repeatedly grabs images from the buffer until only the latest image is left, slowing the process down slightly but greatly improving the turret’s behavior:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# grabs several images from buffer to attempt to clear out old images
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;clear_buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;webcam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;retrieve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ValueError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'no more images in buffer, mate'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The webcam is set up for use with OpenCV via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self.webcam = cv2.VideoCapture(int(self.opts.camera))&lt;/code&gt; within the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Camera&lt;/code&gt; class’s initializer, and frames are captured like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# on Windows and OS X, use OpenCV to grab latest camera frame and store in self.current_frame
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;webcam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grab&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ValueError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'frame grab failed'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clear_buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;retval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;most_recent_frame&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;webcam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;retrieve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;retval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ValueError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'frame capture failed'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_frame&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;most_recent_frame&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We’re currently using OpenCV for photo capture in OS X as well, though it doesn’t seem to work as well as in Windows, so we’re looking for alternative tools we can use to capture photos from within the script.&lt;/p&gt;

&lt;h3 id=&quot;step-four-face-recognition-with-opencv&quot;&gt;Step Four. Face recognition with OpenCV&lt;/h3&gt;

&lt;p&gt;Once a photo is captured, it’s taken to OpenCV for face detection. A lot of things are happening in this method, so I’ve tried to annotate it as much as possible. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw_reticule&lt;/code&gt; is a helper method that draws targets of various styles.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;face_detect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
   &lt;span class=&quot;c1&quot;&gt;# load image, then resize it to specified size
&lt;/span&gt;   &lt;span class=&quot;n&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_frame&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;img_w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;img_h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image_dimensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'x'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cv2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;img_w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;img_h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

   &lt;span class=&quot;c1&quot;&gt;# initialize classifier with training set of faces
&lt;/span&gt;   &lt;span class=&quot;n&quot;&gt;face_filter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cv2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CascadeClassifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;haar_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

   &lt;span class=&quot;c1&quot;&gt;# detect faces
&lt;/span&gt;   &lt;span class=&quot;n&quot;&gt;faces&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;face_filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detectMultiScale&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minNeighbors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

   &lt;span class=&quot;c1&quot;&gt;# convert to grayscale then back, so that we can draw red targets over a grayscale
&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# photo, for an especially ominous effect
&lt;/span&gt;   &lt;span class=&quot;n&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cv2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cvtColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cv2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;COLOR_RGB2GRAY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cv2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cvtColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cv2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;COLOR_GRAY2BGR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

   &lt;span class=&quot;c1&quot;&gt;# sort faces by size (we only use the biggest face for adjusting the camera)
&lt;/span&gt;   &lt;span class=&quot;n&quot;&gt;faces&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;face&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;face&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;face&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

   &lt;span class=&quot;n&quot;&gt;x_adj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_adj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;faces&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;face_detected&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# draw a rectangle around all faces except last face
&lt;/span&gt;      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;faces&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
         &lt;span class=&quot;n&quot;&gt;draw_reticule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;box&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# get last face, draw target, and calculate distance from center
&lt;/span&gt;      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;faces&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;draw_reticule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;170&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;corners&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;x_adj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;img_w&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;img_w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;y_adj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;img_h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;img_h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;face_detected&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;

   &lt;span class=&quot;c1&quot;&gt;# output the modified image so that we can display it
&lt;/span&gt;   &lt;span class=&quot;n&quot;&gt;cv2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imwrite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;processed_img_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

   &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;face_detected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x_adj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_adj&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;step-five-displaying-the-target&quot;&gt;Step Five. Displaying the target&lt;/h3&gt;

&lt;p&gt;After OpenCV has detected any faces, we display the modified image (converted to grayscale, with red targets drawn over faces).&lt;/p&gt;

&lt;p&gt;This method has a great deal of platform-dependent code, to make it play equally nicely with Linux, OS X, and Windows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;In Linux, we open up ImageMagick display windows. These windows do not refresh automatically, so we kill any existing windows each time we open a new one.&lt;/li&gt;
  &lt;li&gt;In OS X, we open a Preview window. Conveniently, calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open -a Preview [path]&lt;/code&gt; refreshes the current Preview window.&lt;/li&gt;
  &lt;li&gt;In Windows, we open Windows Photo Viewer (this might not work in older versions of Windows). It refreshes itself automatically, so
we only open a window the first time &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Camera.display&lt;/code&gt; is called.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# display the OpenCV-processed images
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;platform&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'linux2'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Linux: display with ImageMagick
&lt;/span&gt;      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_image_viewer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
         &lt;span class=&quot;n&quot;&gt;subprocess&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'killall'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_image_viewer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;subprocess&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;display &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;processed_img_file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;' &amp;amp;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shell&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_image_viewer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'display'&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;platform&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'darwin'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# OS X: display with Preview
&lt;/span&gt;      &lt;span class=&quot;n&quot;&gt;subprocess&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'open -a Preview '&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;processed_img_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shell&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_image_viewer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'Preview'&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Windows: display with Windows Photo Viewer
&lt;/span&gt;      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_image_viewer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
         &lt;span class=&quot;n&quot;&gt;viewer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'rundll32 &quot;C:\Program Files\Windows Photo Viewer\PhotoViewer.dll&quot; ImageView_Fullscreen'&lt;/span&gt;
         &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_image_viewer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subprocess&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Popen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'%s %s\%s'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;viewer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getcwd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
               &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;processed_img_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;step-six-aiming-and-firing&quot;&gt;Step Six. Aiming and firing&lt;/h3&gt;

&lt;p&gt;If a face is detected, the turret adjusts itself to try to bring the face into the center of its field of view. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Camera.face_detect&lt;/code&gt; method returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x_adj&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y_adj&lt;/code&gt;, which correspond to the distance from the center of the most prominent face (expressed as a fraction of the total width and height of the photo, respectively). These values are passed to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Turret.adjust&lt;/code&gt; method:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# adjusts the turret's position (units are fairly arbitary but work ok)
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;adjust&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right_dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;down_dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;right_seconds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right_dist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.64&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;down_seconds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;down_dist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.48&lt;/span&gt;

   &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right_seconds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;launcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;turretRight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;right_seconds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;launcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;turretStop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right_seconds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;launcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;turretLeft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right_seconds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;launcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;turretStop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

   &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;down_seconds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;launcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;turretDown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;down_seconds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;launcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;turretStop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;down_seconds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;launcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;turretUp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;down_seconds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;launcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;turretStop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note that the only way to move the turret a certain distance is to estimate how long the turret’s rotation would last and send it commands to move and stop with the appropriate timing.&lt;/p&gt;

&lt;p&gt;Finally, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Turret.ready_aim_fire&lt;/code&gt; detects if the face was close enough to the center of the camera to fire, turning on the turret’s LED as a warning before firing a missile (if the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--disarm&lt;/code&gt; flag is passed to the script, the LED is turned on, but no missile is fired). Then the loop continues, until the turret has fired all four of its missiles:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# turn on LED if face detected in range, and fire missiles if armed
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ready_aim_fire&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x_adj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_adj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;face_detected&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x_adj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;05&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y_adj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;turret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;launcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ledOn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;armed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
         &lt;span class=&quot;n&quot;&gt;turret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;launcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;turretFire&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
         &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# roughly how long it takes to fire
&lt;/span&gt;         &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;missiles_remaining&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'Missile fired! Estimated '&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;missiles_remaining&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;' missiles remaining.'&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;missiles_remaining&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;raw_input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Ammunition depleted. Awaiting order to continue assault. [ENTER]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;missiles_remaining&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'Turret trained but not firing because of the --disarm directive.'&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;turret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;launcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ledOff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;try-it-yourself&quot;&gt;Try it yourself!&lt;/h3&gt;

&lt;p&gt;If you have a Dream Cheeky brand USB missile launcher (though it wouldn’t take much work to support other brands of missile launchers), a compact webcam, and a desire to build your very own defense system for your home or workplace, check out &lt;a href=&quot;https://github.com/AlexNisnevich/sentinel&quot;&gt;our GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We’ve gotten Sentinel working on Windows, OS X, and several Linux distros, though installing the dependencies (OpenCV, PyUSB, and others, depending on platform) can take some work.&lt;/p&gt;

&lt;p&gt;We’re currently hard at work on some more features, including:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;different modes of operation (such as a “sweep mode”, in which the turret continually pans until it locates a face, rather than staying idle when it doesn’t see a face)&lt;/li&gt;
  &lt;li&gt;a “kill-cam” feature that stores the pictures that it takes right as it shoots a target&lt;/li&gt;
  &lt;li&gt;easier installation of dependencies (especially on Windows)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Got any comments, questions, or suggestions? Be sure to let me know, either here or on Github.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Installing a Brother printer on 64-bit Ubuntu/Debian</title>
   
    <link href="http://alex.nisnevich.com/blog/2013/02/07/installing_brother_printer.html"/>
   
   <updated>2013-02-07T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2013/02/07/installing_brother_printer</id>
   
    <content type="html">&lt;p&gt;The other day I had to reinstall Linux Mint Debian Edition. The reinstall process went smoothly overall,
with the only problem being, of all things, the driver for my Brother printer.&lt;/p&gt;

&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;/h3&gt;

&lt;p&gt;The problem is that &lt;a href=&quot;http://welcome.solutions.brother.com/bsc/public_s/id/linux/en/download_prn.html&quot;&gt;Brother’s official Linux printer drivers&lt;/a&gt;
are all 32-bit, and don’t appear to play nicely with 64-bit Debian-based distributions.&lt;/p&gt;

&lt;p&gt;When I tried installing the driver directly, I got an incompatible architecture error:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;dpkg &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; cupswrapperHL2270DW-2.0.4-2.i386.deb
dpkg: error processing cupswrapperHL2270DW-2.0.4-2.i386.deb &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--install&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:
 package architecture &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;i386&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; does not match system &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;amd64&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
Errors were encountered &lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;processing:
 cupswrapperHL2270DW-2.0.4-2.i386.deb&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, the recommended way to resolve this is to use &lt;a href=&quot;http://wiki.debian.org/Multiarch/HOWTO&quot;&gt;MultiArch&lt;/a&gt;, Debian’s
system for handling applications and libraries of multiple architectures on one system. However, MultiArch is still
rather immature, and whenever I’ve tried to use it I’ve ended up in an unresolvable dependency hell. I wanted to
find a better way to deal with this.&lt;/p&gt;

&lt;p&gt;My first instinct was to suppress the incompatible architecture error using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--force-architecture&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--force-all&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;dpkg &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--force-all&lt;/span&gt; cupswrapperHL2270DW-2.0.4-2.i386.deb
dpkg: warning: overriding problem because &lt;span class=&quot;nt&quot;&gt;--force&lt;/span&gt; enabled:
 package architecture &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;i386&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; does not match system &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;amd64&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Reading database ... 171933 files and directories currently installed.&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
Preparing to replace cupswrapperhl2270dw 2.0.4-2 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;using cupswrapperHL2270DW-2.0.4-2.i386.deb&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; ...
Restarting Common Unix Printing System: cupsd.
Unpacking replacement cupswrapperhl2270dw ...
dpkg: cupswrapperhl2270dw: dependency problems, but configuring anyway as you requested:
 cupswrapperhl2270dw depends on libc6 &lt;span class=&quot;o&quot;&gt;(&amp;gt;=&lt;/span&gt; 2.3.4-1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; however:
  Package libc6:i386 is not installed.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Hmm, no luck yet - it looks like the driver depends on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libc6:i386&lt;/code&gt; package, which I can’t install without MultiArch.&lt;/p&gt;

&lt;p&gt;Interestingly, the &lt;a href=&quot;http://welcome.solutions.brother.com/bsc/public_s/id/linux/en/faq_prn.html#f00081&quot;&gt;Brother official FAQ&lt;/a&gt;
suggests installing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib32stdc++6&lt;/code&gt; package (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ia32-libs&lt;/code&gt; on Ubuntu) and using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--force-architecture&lt;/code&gt;, but this still didn’t
work for me, even after installing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib32stdc++6&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;the-solution&quot;&gt;The Solution&lt;/h3&gt;

&lt;p&gt;Fortunately, a solution does exist, though it’s a bit hackish: you need to remove the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libc&lt;/code&gt; dependency from the package.&lt;/p&gt;

&lt;p&gt;There are a few ways to do this:&lt;/p&gt;

&lt;h4 id=&quot;option-1-manual-modification-of-the-package&quot;&gt;Option 1: Manual modification of the package&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;https://bugs.launchpad.net/ubuntu/+source/cups/+bug/701856/comments/20&quot;&gt;Yousry Abdallah recommends&lt;/a&gt;
removing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libc&lt;/code&gt; dependency from the driver:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ol&gt;
    &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dpkg -x [package].deb common&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dpkg --control [package].deb&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vim DEBIAN/control&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;remove the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Dependency: libc (...&quot;&lt;/code&gt; line (move to line than press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;'dd'&lt;/code&gt; than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ESC:x&lt;/code&gt;)&lt;/li&gt;
    &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cp -a DEBIAN/ common/&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dpkb -b common [package].deb&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dpkg --force-all -i [package].deb&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rm -rf common DEBIAN&lt;/code&gt;&lt;/li&gt;
  &lt;/ol&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;option-2-official-patch-brother-hl-2270dw-only&quot;&gt;Option 2: Official patch (Brother HL-2270DW only)&lt;/h4&gt;

&lt;p&gt;If you have a Brother HL-2270DW printer like me, then you’re in luck: Brother has released a patch to resolve the issue. You can
follow the instructions &lt;a href=&quot;http://welcome.solutions.brother.com/bsc/public_s/id/linux/en/faq_prn.html#f00098&quot;&gt;here&lt;/a&gt; to use it.&lt;/p&gt;

&lt;p&gt;Or you can simply download the &lt;a href=&quot;https://docs.google.com/leaf?id=0BxKNugMxlGF7NzQwYTcyNmYtZWMxNC00MzJlLWEwZGEtM2QyNzgwN2ZhYWZh&amp;amp;hl=en_US&quot;&gt;already-patched CUPS driver&lt;/a&gt;,
which has been patched by &lt;a href=&quot;http://chadchenault.blogspot.com/2011/09/brother-hl-2270dw-printer-driver.html&quot;&gt;Chad Chenault&lt;/a&gt;. If you would rather use LPR, a &lt;a href=&quot;https://docs.google.com/leaf?id=0BxKNugMxlGF7MjQ3ODI4MzEtMGMyYy00ZGVlLThiMjAtZjdiMjZjZjkxOWE3&amp;amp;hl=en_US&quot;&gt;patched LPR driver&lt;/a&gt; is also available.&lt;/p&gt;

&lt;p&gt;Remember that you need to install the packages using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--force-all&lt;/code&gt; option:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; dpkg &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--force-all&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;package.deb]&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I’ve tested the CUPS driver on Linux Mint Debian and it works perfectly. Both drivers should also work on Ubuntu 11.04 and later.&lt;/p&gt;

&lt;h3 id=&quot;finishing-the-installation&quot;&gt;Finishing the installation&lt;/h3&gt;

&lt;p&gt;If you’re using the printer via USB, then everything should work after installing the package. Make sure your printer is plugged in,
then go to &lt;a href=&quot;http://localhost:631/printers&quot;&gt;http://localhost:631/printers&lt;/a&gt; and try printing a test page.&lt;/p&gt;

&lt;p&gt;However, if you’re using a network printer, then there are still a few steps left. You can
&lt;a href=&quot;http://community.linuxmint.com/tutorial/view/194&quot;&gt;follow this tutorial&lt;/a&gt; or &lt;a href=&quot;http://www.youtube.com/watch?v=pqpWhnjemLc&quot;&gt;watch this video&lt;/a&gt;
(skip to the part after the package is installed).&lt;/p&gt;

&lt;p&gt;Good luck!&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Quantifying Linguistic Diversity</title>
   
    <link href="http://alex.nisnevich.com/blog/2013/01/11/quantifying_linguistic_diversity.html"/>
   
   <updated>2013-01-11T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2013/01/11/quantifying_linguistic_diversity</id>
   
    <content type="html">&lt;script src=&quot;https://www.gstatic.com/charts/loader.js&quot;&gt;&lt;/script&gt;

&lt;h3&gt;Adventures in Reinventing the Wheel&lt;/h3&gt;

&lt;img class=&quot;figure&quot; src=&quot;/blog/images/linguistic_diversity_1.png&quot;&gt;

&lt;p&gt;A few days ago, one of my friends posted this status on Facebook, and it got me thinking: how could I test a hypothesis like this? Supposing I were to use GDP per capita as a measure of affluence, I would simply have to compare it to a measure of linguistic diversity, and compute the correlation coefficient.&lt;/p&gt;

&lt;p&gt;But does such a measure of linguistic diversity exist? I wasn't aware of one, so I decided to come up with my own, which I would call the &lt;strong&gt;Linguistic Diversity Index (LDI)&lt;/strong&gt;. I thought about a few potential ways to measure the diversity of languages in a country, and ulimately settled on this definition: &lt;em&gt;the percentage probability that two random residents of a given country have different native languages&lt;/em&gt;. Compared to other potential measurements (such as the popularity of the most common language within a country), this definition for the LDI has the advantage of taking into account every language spoken in a country, while still being easy to visualize.&lt;/p&gt;

&lt;p&gt;Computing the LDI is pretty straightforward. Given a country with population &lt;b&gt;P&lt;/b&gt; and &lt;b&gt;n&lt;/b&gt; languages, with &lt;b&gt;L&lt;sub&gt;1&lt;/sub&gt;&lt;/b&gt;, &lt;b&gt;L&lt;sub&gt;2&lt;/sub&gt;&lt;/b&gt;, ..., &lt;b&gt;L&lt;sub&gt;n&lt;/sub&gt;&lt;/b&gt; speakers, respectively, the LDI of the country is:&lt;/p&gt;

&lt;img class=&quot;figure&quot; src=&quot;/blog/images/linguistic_diversity_3.png&quot;&gt;

&lt;p&gt;Now, if I had bothered to do a Google search at this point, I would have found out that there actually is such a thing as a &lt;a href=&quot;http://en.wikipedia.org/wiki/Linguistic_diversity_index&quot;&gt;Linguistic Diversity Index&lt;/a&gt; (also known as Greenberg’s diversity index), and, moreover, it's defined more or less the same way that I defined it (namely, &lt;em&gt;the probability that any two people of the country selected at random would have different mother tongues&lt;/em&gt;), and the data listed on the Wikipedia page is very close to what I ultimately obtained, as it was calculated using similar data. But I didn't realize that an LDI already existed at the time, and so I ended up essentially duplicating the existing work on the topic.&lt;/p&gt;

&lt;h3&gt;Scraping Ethnologue Data with Mechanize&lt;/h3&gt;

&lt;p&gt;Let me take some time to show how I collected the data for the LDI. If you'd rather just skip ahead to the pretty charts, &lt;a href=&quot;#results&quot;&gt;go right ahead&lt;/a&gt;. I won't be mad.&lt;/p&gt;

&lt;p&gt;Still here? Awesome.&lt;/p&gt;

&lt;p&gt;In order to calculate the LDI, I needed to find the most accurate data possible on the number of speakers of different languages within each country, and so I turned to &lt;a href=&quot;http://www.ethnologue.com&quot;&gt;Ethnologue&lt;/a&gt;, a database of statistics on ~7000 languages. Each country's page in Ethnologue looks like this (with the red boxes indicating the numbers that I want to extract):&lt;/p&gt;

&lt;img class=&quot;figure&quot; src=&quot;/blog/images/linguistic_diversity_2.png&quot;&gt;

&lt;p&gt;Doing this by hand would be incredibly tedious, so I needed to find a way to scrape all of the country pages on Ethnologue. I wasn't sure what the best way to do this in Ruby would be, so I asked my trusty assistant, &lt;a href=&quot;https://github.com/gleitz/howdoi&quot;&gt;howdoi&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; howdoi scrape web page &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;ruby
take a look at this library http://mechanize.rubyforge.org/&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ah, excellent! &lt;a href=&quot;http://mechanize.rubyforge.org/&quot;&gt;Mechanize&lt;/a&gt; does just what I want, and has spiffy syntax to boot (for example, &lt;code&gt;languages = country_page / &quot;#main table p&quot;&lt;/code&gt; literally &lt;em&gt;divides&lt;/em&gt; the page into an array of sections matched by the CSS selector &lt;code&gt;#main table p&lt;/code&gt;). The full script that I used to gather all of the data I needed clocks in at around 50 lines, and the trickiest bit was coming up with the appropriate regexes to extract the right things:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'mechanize'&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;agent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Mechanize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;language_regex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Regexp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/]\r\n\t\t\t\t\t\t([0-9,]*)(\.| )/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;index_page&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'http://www.ethnologue.com/country_index.asp?place=all'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;index_page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;links_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:href&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/show_country.asp/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;country_link&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;country&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;country_link&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;speakers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;country_page&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'http://www.ethnologue.com/'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;country_link&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# get language speakers from table&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;languages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;country_page&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;#main table p&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;languages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;speakers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;languages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;language_regex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gsub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;','&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# get immigrant language speakers&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;country_page&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;#main blockquote&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;immigrant_languages_description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;scan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/Immigrant languages:.*?\./&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;immigrant_languages_description&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;immigrant_languages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;immigrant_languages_description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;scan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/\(([0-9,]*)\)/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                                                         &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gsub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;','&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;speakers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;immigrant_languages&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# get language speakers from subpages (if any)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;country_page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;links_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:href&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/show_country.asp/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subpage_link&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;subpage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'http://www.ethnologue.com/'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subpage_link&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;subpage_languages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subpage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;#main table p&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subpage_languages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;subpage_speakers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subpage_languages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;language_regex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gsub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;','&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;speakers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subpage_speakers&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# compute diversity index&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;total_speakers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;speakers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;diversity_index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_speakers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
                      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;speakers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_speakers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
                      &lt;span class=&quot;s2&quot;&gt;&quot;Insufficient data for meaningful answer&quot;&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;country&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ljust&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\t&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;diversity_index&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note that I make a few simplifying assumptions here. For one thing, I didn't use Ethnologue's given value for the total population of each country, but rather set &lt;b&gt;P = L&lt;sub&gt;1&lt;/sub&gt; + L&lt;sub&gt;2&lt;/sub&gt; + ... + L&lt;sub&gt;n&lt;/sub&gt;&lt;/b&gt;. This was important because I've noted Ethnologue's total population values to typically be more out of date than the language data, and I didn't want LDI values below zero, which could occur if, say, &lt;b&gt;L&lt;sub&gt;1&lt;/sub&gt; &gt; P&lt;/b&gt;. Ethnologue language data is also imperfect - for example, some minor languages don't have their speaker count listed - but I assumed that the data was accurate enough as a whole for my computed LDI values to be meaningful.&lt;/p&gt;

&lt;a name=&quot;results&quot;&gt;&lt;/a&gt;
&lt;h3&gt;Results&lt;/h3&gt;

&lt;p&gt;After running the script, I got &lt;a href=&quot;https://gist.github.com/raw/4509555/9088d048b93bc9c267cccfb2c4455bab989671c6/output&quot;&gt;these results&lt;/a&gt;. But raw data is boring. Let's make it into a map!&lt;/p&gt;

&lt;div id=&quot;map_canvas&quot; class=&quot;chart&quot;&gt;&lt;/div&gt;

&lt;p&gt;There's some interesting patterns that can be seen from the map. It appears that countries in Sub-Saharan Africa and South and Southeast Asia tend to have the highest rates of linguistic diversity (Papua New Guinea is at the very top, with over 600 different languages spoken and a diversity index of 99.03). All in all, thoughout the world, diversity appears to be the norm, rather than homogeneity: even Europe has its fair share of high-LDI countries (admittedly, a lot of this is made murky by the question of what is a language and what is a dialect).&lt;/p&gt;

&lt;p&gt;Now I finally have all of the data that I need to test my friend's hypothesis about the relationship between language diversity and affluence. Using &lt;a href=&quot;http://en.wikipedia.org/wiki/List_of_countries_by_GDP_(PPP)_per_capita&quot;&gt;GDP (PPP) per capita data&lt;/a&gt; from the World Bank, I get the following plot:&lt;/p&gt;

&lt;div id=&quot;chart_div&quot; class=&quot;chart&quot;&gt;&lt;/div&gt;

&lt;p&gt;There is a weak negative correlation (&lt;b&gt;&amp;#x3C1; = -0.247&lt;/b&gt;), suggesting that the relationship between language diversity and affluence is tenuous at best. Interestingly, while there are a great deal of countries that are both very poor and highly linguistically diverse, 7 of the 9 wealhiest countries (with the exceptions being Norway and the United States) all have LDI in the &lt;b&gt;48-78&lt;/b&gt; range, perhaps suggesting that some level of linguistic diversity is economically useful in establishing a nation as a trade hub. Of course, correlation does not imply causation, and there aren't many conclusions that can reasonably be drawn from this chart without more outside information.&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;All told, this was a fun little project. It was unfortunate that I only found out later that the LDI already exists, and so my data collection was unnecessary, but it was nice to get some experience in scraping data, and I think that I've come up with some novel ways to present language diversity data. In the end, I feel that there is certainly room for more research to be done in the interconnection between the linguistic and economic spheres of countries.&lt;/p&gt;

&lt;script type=&quot;text/javascript&quot;&gt;
	google.charts.load(&quot;current&quot;, {packages:[&quot;corechart&quot;]});
	google.charts.setOnLoadCallback(drawChart);
	function drawChart() {
		var data = google.visualization.arrayToDataTable([
			['ID', 'Linguistic Diversity Index', 'GDP (PPP)/capita in Int$', 'Region'],
			['Luxembourg', 48.86, 89012, 'Europe'],
			['Qatar', 60.79, 88314, 'Asia'],
			['Singapore', 77.33, 60688, 'Asia'],
			['Norway', 7.39, 60405, 'Europe'],
			['Kuwait', 55.64, 54283, 'Asia'],
			['Brunei', 58.53, 51760, 'Asia'],
			['Switzerland', 57.73, 51262, 'Europe'],
			['United States', 31.87, 48112, 'North America'],
			['United Arab Emirates', 77.71, 47893, 'Asia'],
			['Netherlands', 29.22, 42772, 'Europe'],
			['Austria', 53.48, 42196, 'Europe'],
			['Ireland', 16.54, 41682, 'Europe'],
			['Sweden', 14.65, 41467, 'Europe'],
			['Denmark', 5.48, 40908, 'Europe'],
			['Canada', 59.94, 40370, 'Europe'],
			['Australia', 12.49, 39721, 'Oceania'],
			['Germany', 36.92, 39491, 'Europe'],
			['Belgium', 74.74, 38768, 'Europe'],
			['Finland', 14.97, 37464, 'Europe'],
			['Iceland', 0.00, 36485, 'Europe'],
			['Equatorial Guinea', 41.68, 36202, 'Africa'],
			['United Kingdom', 13.41, 35657, 'Europe'],
			['France', 26.81, 35246, 'Europe'],
			['Japan', 3.32, 34314, 'Asia'],
			['Italy', 58.61, 32647, 'Europe'],
			['Cyprus', 33.15, 32254, 'Europe'],
			['Spain', 51.18, 32045, 'Europe'],
			['Bahamas, The', 38.62, 31978, 'North America'],
			['South Korea', 0.30, 30286, 'Asia'],
			['New Zealand', 10.70, 30057, 'Oceania'],
			['Oman', 69.29, 28684, 'Asia'],
			['Israel', 66.52, 27825, 'Asia'],
			['Malta', 41.19, 27284, 'Europe'],
			['Slovenia', 17.41, 26954, 'Europe'],
			['Seychelles', 6.67, 26241, 'Africa'],
			['Czech Republic', 14.59, 26208, 'Europe'],
			['Greece', 14.38, 25850, 'Europe'],
			['Portugal', 2.20, 25372, 'Europe'],
			['Trinidad and Tobago', 69.64, 25074, 'North America'],
			['Saudi Arabia', 13.80, 24268, 'Asia'],
			['Slovakia', 23.66, 23910, 'Europe'],
			['Bahrain', 66.29, 23645, 'Asia'],
			['Estonia', 45.71, 21995, 'Europe'],
			['Hungary', 2.40, 21663, 'Europe'],
			['Poland', 6.61, 21261, 'Europe'],
			['Russia', 25.00, 21246, 'Europe'],
			['Lithuania', 34.06, 20321, 'Europe'],
			['Croatia', 21.06, 19469, 'Europe'],
			['Barbados', 9.10, 19320, 'North America'],
			['Antigua and Barbuda', 24.84, 18492, 'North America'],
			['Latvia', 58.35, 17569, 'Europe'],
			['Argentina', 23.31, 17554, 'South America'],
			['Chile', 3.50, 17310, 'South America'],
			['Saint Kitts and Nevis', 1.98, 17226, 'North America'],
			['Turkey', 27.12, 17110, 'Asia'],
			['Libya', 50.02, 16897, 'Africa'],
			['Malaysia', 74.36, 16051, 'Asia'],
			['Gabon', 76.19, 15852, 'Africa'],
			['Panama', 32.37, 15589, 'South America'],
			['Mexico', 13.88, 15266, 'North America'],
			['Romania', 16.88, 15139, 'Europe'],
			['Uruguay', 9.23, 15078, 'South America'],
			['Belarus', 39.66, 14938, 'Europe'],
			['Bulgaria', 26.34, 14825, 'Europe'],
			['Botswana', 51.23, 14746, 'Africa'],
			['Lebanon', 16.12, 14609, 'Asia'],
			['Mauritius', 58.70, 14420, 'Africa'],
			['Palau', 7.75, 13758, 'Oceania'],
			['Montenegro', 56.71, 13432, 'Europe'],
			['Dominica', 31.32, 13288, 'North America'],
			['Kazakhstan', 69.86, 13099, 'Asia'],
			['Venezuela', 5.06, 12749, 'South America'],
			['Costa Rica', 4.95, 12157, 'North America'],
			['Serbia', 62.84, 11883, 'Europe'],
			['Brazil', 10.34, 11640, 'South America'],
			['Saint Lucia', 13.43, 11597, 'North America'],
			['Iran', 82.20, 11508, 'Asia'],
			['Macedonia', 57.81, 11258, 'Europe'],
			['South Africa', 87.41, 10960, 'Africa'],
			['Grenada', 6.43, 10837, 'North America'],
			['Saint Vincent and the Grenadines', 0.86, 10715, 'North America'],
			['Peru', 38.76, 10234, 'South America'],
			['Azerbaijan', 45.51, 10067, 'Asia'],
			['Colombia', 3.69, 10033, 'South America'],
			['Dominican Republic', 5.34, 9796, 'North America'],
			['Turkmenistan', 38.56, 9420, 'Asia'],
			['Tunisia', 1.16, 9351, 'Africa'],
			['Bosnia and Herzegovina', 65.95, 9076, 'Europe'],
			['Maldives', 0.78, 8871, 'Africa'],
			['Albania', 57.74, 8866, 'Europe'],
			['Ecuador', 29.12, 8669, 'South America'],
			['Algeria', 31.70, 8655, 'Africa'],
			['Thailand', 74.06, 8646, 'Asia'],
			['China', 50.94, 8400, 'Asia'],
			['Suriname', 78.84, 7891, 'Africa'],
			['Ukraine', 49.49, 7208, 'Europe'],
			['Jamaica', 1.11, 7083, 'North America'],
			['El Salvador', 0.43, 6831, 'North America'],
			['Namibia', 80.97, 6779, 'Africa'],
			['Belize', 76.93, 6672, 'North America'],
			['Egypt', 53.58, 6281, 'Africa'],
			['Swaziland', 21.03, 6057, 'Africa'],
			['Jordan', 49.56, 5966, 'Asia'],
			['Angola', 81.29, 5920, 'Africa'],
			['Bhutan', 88.37, 5846, 'Asia'],
			['Armenia', 15.94, 5789, 'Asia'],
			['Sri Lanka', 31.96, 5582, 'Asia'],
			['Paraguay', 35.20, 5501, 'South America'],
			['Georgia', 58.21, 5465, 'Asia'],
			['Syria', 52.70, 5252, 'Asia'],
			['Bolivia', 68.06, 5099, 'South America'],
			['Morocco', 46.58, 4952, 'Africa'],
			['Guatemala', 68.99, 4928, 'North America'],
			['Tonga', 1.41, 4886, 'Oceania'],
			['Fiji', 60.85, 4757, 'Oceania'],
			['Mongolia', 33.15, 4742, 'Asia'],
			['Indonesia', 81.63, 4636, 'Asia'],
			['Samoa', 0.20, 4475, 'Oceania'],
			['Vanuatu', 97.35, 4451, 'Oceania'],
			['Congo, Rep.', 85.72, 4360, 'Africa'],
			['Philippines', 85.53, 4119, 'Asia'],
			['Cape Verde', 6.98, 4095, 'Africa'],
			['Honduras', 5.57, 4047, 'North America'],
			['Iraq', 67.45, 3864, 'Asia'],
			['Nicaragua', 8.23, 3812, 'North America'],
			['India', 94.01, 3627, 'Asia'],
			['Guyana', 7.96, 3438, 'South America'],
			['Micronesia, Fed. Sts.', 77.21, 3412, 'Oceania'],
			['Vietnam', 24.23, 3412, 'Asia'],
			['Moldova', 58.98, 3369, 'Europe'],
			['Uzbekistan', 43.69, 3287, 'Asia'],
			['Solomon Islands', 96.71, 2923, 'Oceania'],
			['Laos', 67.37, 2790, 'Asia'],
			['Pakistan', 76.20, 2745, 'Asia'],
			['Papua New Guinea', 99.03, 2676, 'Oceania'],
			['Mauritania', 18.34, 2554, 'Africa'],
			['Nigeria', 87.58, 2533, 'Africa'],
			['Kyrgyzstan', 67.01, 2399, 'Asia'],
			['Cameroon', 94.63, 2359, 'Africa'],
			['Cambodia', 16.93, 2358, 'Asia'],
			['Kiribati', 3.30, 2337, 'Oceania'],
			['Yemen', 57.89, 2333, 'Asia'],
			['Sudan &amp;  South Sudan', 54.47, 2325, 'Africa'],
			['Tajikistan', 48.46, 2324, 'Asia'],
			['Djibouti', 57.06, 2296, 'Africa'],
			['São Tomé and Príncipe', 38.96, 2077, 'Africa'],
			['Senegal', 77.47, 1967, 'Africa'],
			['Ghana', 80.51, 1871, 'Africa'],
			['Gambia, The', 78.05, 1809, 'Africa'],
			['Côte d\'Ivoire', 91.62, 1789, 'Africa'],
			['Bangladesh', 38.66, 1777, 'Asia'],
			['Kenya', 87.66, 1710, 'Africa'],
			['Lesotho', 26.04, 1703, 'Africa'],
			['Zambia', 87.77, 1621, 'Africa'],
			['Benin', 92.11, 1617, 'Africa'],
			['East Timor', 89.68, 1578, 'Asia'],
			['Chad', 94.43, 1521, 'Africa'],
			['Tanzania', 94.68, 1512, 'Africa'],
			['Uganda', 92.78, 1345, 'Africa'],
			['Burkina Faso', 77.28, 1301, 'Africa'],
			['Rwanda', 0.40, 1282, 'Africa'],
			['Nepal', 73.75, 1252, 'Asia'],
			['Guinea-Bissau', 87.14, 1243, 'Africa'],
			['Haiti', 0.02, 1171, 'North America'],
			['Afghanistan', 74.06, 1139, 'Asia'],
			['Guinea', 75.41, 1124, 'Africa'],
			['Comoros', 54.54, 1110, 'Africa'],
			['Ethiopia', 86.40, 1109, 'Africa'],
			['Mali', 87.40, 1091, 'Africa'],
			['Togo', 89.73, 1049, 'Africa'],
			['Mozambique', 93.23, 975, 'Africa'],
			['Madagascar', 72.12, 966, 'Africa'],
			['Malawi', 52.47, 893, 'Africa'],
			['Sierra Leone', 82.16, 871, 'Africa'],
			['Central African Republic', 95.90, 810, 'Africa'],
			['Niger', 63.98, 727, 'Africa'],
			['Burundi', 0.41, 604, 'Africa'],
			['Eritrea', 62.71, 585, 'Africa'],
			['Liberia', 91.65, 585, 'Africa'],
			['Congo, Dem. Rep.', 94.91, 373, 'Africa']
		]);

		var options = {
			title: 'Linguistic Diversity vs GDP/capita',
			hAxis: {title: 'Linguistic Diversity Index (% chance that two residents have different native languages)'},
			vAxis: {
				title: 'GDP (PPP)/capita in Int$ (World Bank, 2005-2011)',
				logScale: true,
				minValue: 100,
				maxValue: 100000,
				gridlines: {count: 4}
			},
			sizeAxis: {minSize: 5, maxSize: 5},
			bubble: {textStyle: {color: 'transparent'}}
		};

		var chart = new google.visualization.BubbleChart(document.getElementById('chart_div'));
		chart.draw(data, options);
	}
&lt;/script&gt;
&lt;script type='text/javascript'&gt;
	google.load('visualization', '1', {'packages': ['geochart']});
	google.setOnLoadCallback(drawMap);

	function drawMap() {
		var data = google.visualization.arrayToDataTable([
			['Country', 'Linguistic Diversity Index'],
			['Luxembourg', 48.86],
			['Qatar', 60.79],
			['Singapore', 77.33],
			['Norway', 7.39],
			['Kuwait', 55.64],
			['Brunei', 58.53],
			['Switzerland', 57.73],
			['United States', 31.87],
			['United Arab Emirates', 77.71],
			['Netherlands', 29.22],
			['Austria', 53.48],
			['Ireland', 16.54],
			['Sweden', 14.65],
			['Denmark', 5.48],
			['Canada', 59.94],
			['Australia', 12.49],
			['Germany', 36.92],
			['Belgium', 74.74],
			['Finland', 14.97],
			['Iceland', 0.00],
			['Equatorial Guinea', 41.68],
			['United Kingdom', 13.41],
			['France', 26.81],
			['Japan', 3.32],
			['Italy', 58.61],
			['Cyprus', 33.15],
			['Spain', 51.18],
			['Bahamas, The', 38.62],
			['South Korea', 0.30],
			['New Zealand', 10.70],
			['Oman', 69.29],
			['Israel', 66.52],
			['Malta', 41.19],
			['Slovenia', 17.41],
			['Seychelles', 6.67],
			['Czech Republic', 14.59],
			['Greece', 14.38],
			['Portugal', 2.20],
			['Trinidad and Tobago', 69.64],
			['Saudi Arabia', 13.80],
			['Slovakia', 23.66],
			['Bahrain', 66.29],
			['Estonia', 45.71],
			['Hungary', 2.40],
			['Poland', 6.61],
			['Russia', 25.00],
			['Lithuania', 34.06],
			['Croatia', 21.06],
			['Barbados', 9.10],
			['Antigua and Barbuda', 24.84],
			['Latvia', 58.35],
			['Argentina', 23.31],
			['Chile', 3.50],
			['Saint Kitts and Nevis', 1.98],
			['Turkey', 27.12],
			['Libya', 50.02],
			['Malaysia', 74.36],
			['Gabon', 76.19],
			['Panama', 32.37],
			['Mexico', 13.88],
			['Romania', 16.88],
			['Uruguay', 9.23],
			['Belarus', 39.66],
			['Bulgaria', 26.34],
			['Botswana', 51.23],
			['Lebanon', 16.12],
			['Mauritius', 58.70],
			['Palau', 7.75],
			['Montenegro', 56.71],
			['Dominica', 31.32],
			['Kazakhstan', 69.86],
			['Venezuela', 5.06],
			['Costa Rica', 4.95],
			['Serbia', 62.84],
			['Brazil', 10.34],
			['Saint Lucia', 13.43],
			['Iran', 82.20],
			['Macedonia', 57.81],
			['South Africa', 87.41],
			['Grenada', 6.43],
			['Saint Vincent and the Grenadines', 0.86],
			['Peru', 38.76],
			['Azerbaijan', 45.51],
			['Colombia', 3.69],
			['Dominican Republic', 5.34],
			['Turkmenistan', 38.56],
			['Tunisia', 1.16],
			['Bosnia and Herzegovina', 65.95],
			['Maldives', 0.78],
			['Albania', 57.74],
			['Ecuador', 29.12],
			['Algeria', 31.70],
			['Thailand', 74.06],
			['China', 50.94],
			['Suriname', 78.84],
			['Ukraine', 49.49],
			['Jamaica', 1.11],
			['El Salvador', 0.43],
			['Namibia', 80.97],
			['Belize', 76.93],
			['Egypt', 53.58],
			['Swaziland', 21.03],
			['Jordan', 49.56],
			['Angola', 81.29],
			['Bhutan', 88.37],
			['Armenia', 15.94],
			['Sri Lanka', 31.96],
			['Paraguay', 35.20],
			['Georgia', 58.21],
			['Syria', 52.70],
			['Bolivia', 68.06],
			['Morocco', 46.58],
			['Guatemala', 68.99],
			['Tonga', 1.41],
			['Fiji', 60.85],
			['Mongolia', 33.15],
			['Indonesia', 81.63],
			['Samoa', 0.20],
			['Vanuatu', 97.35],
			['CG', 85.72],
			['Philippines', 85.53],
			['Cape Verde', 6.98],
			['Honduras', 5.57],
			['Iraq', 67.45],
			['Nicaragua', 8.23],
			['India', 94.01],
			['Guyana', 7.96],
			['Micronesia', 77.21],
			['Vietnam', 24.23],
			['Moldova', 58.98],
			['Uzbekistan', 43.69],
			['Solomon Islands', 96.71],
			['Laos', 67.37],
			['Pakistan', 76.20],
			['Papua New Guinea', 99.03],
			['Mauritania', 18.34],
			['Nigeria', 87.58],
			['Kyrgyzstan', 67.01],
			['Cameroon', 94.63],
			['Cambodia', 16.93],
			['Kiribati', 3.30],
			['Yemen', 57.89],
			['Sudan', 54.47],
			['Tajikistan', 48.46],
			['Djibouti', 57.06],
			['Sao Tome and Principe', 38.96],
			['Senegal', 77.47],
			['Ghana', 80.51],
			['Gambia, The', 78.05],
			['Bangladesh', 38.66],
			['Kenya', 87.66],
			['Lesotho', 26.04],
			['Zambia', 87.77],
			['Benin', 92.11],
			['East Timor', 89.68],
			['Chad', 94.43],
			['Tanzania', 94.68],
			['Uganda', 92.78],
			['Burkina Faso', 77.28],
			['Rwanda', 0.40],
			['Nepal', 73.75],
			['Guinea-Bissau', 87.14],
			['Haiti', 0.02],
			['Afghanistan', 74.06],
			['Guinea', 75.41],
			['Comoros', 54.54],
			['Ethiopia', 86.40],
			['Mali', 87.40],
			['Togo', 89.73],
			['Mozambique', 93.23],
			['Madagascar', 72.12],
			['Malawi', 52.47],
			['Sierra Leone', 82.16],
			['Central African Republic', 95.90],
			['Niger', 63.98],
			['Burundi', 0.41],
			['Eritrea', 62.71],
			['Liberia', 91.65],
			['CD', 94.91],
			['Zimbabwe', 51.82],
			['North Korea', 0],
			['Somalia', 34.87],
			['Myanmar', 53.46],
			['Cuba', 0.07],
			['French Guiana', 44.99],
			['CI', 91.62],
			['Greenland', 24.19]
		]);

		var options = {
			dataMode: 'regions',
			width: 700,
			height: 400
		};

		var container = document.getElementById('map_canvas');
		var geomap = new google.visualization.GeoChart(container);
		geomap.draw(data, options);
	};
&lt;/script&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Playing Around with Ruby Hashes</title>
   
    <link href="http://alex.nisnevich.com/blog/2012/07/30/fun_with_ruby_hashes.html"/>
   
   <updated>2012-07-30T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2012/07/30/fun_with_ruby_hashes</id>
   
    <content type="html">&lt;p&gt;As a relative Ruby newbie, one of the things I love about it is how easy and elegant it is to modify existing classes.&lt;/p&gt;

&lt;p&gt;In this post I’d like to mention some of the cool things that I’ve discovered you can do with hashes.&lt;/p&gt;

&lt;h3 id=&quot;arbitrarily-deep-assignment&quot;&gt;Arbitrarily deep assignment&lt;/h3&gt;

&lt;p&gt;Suppose I’m using a hash to store data that has a complicated structure. I want to be able to do something like&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:stats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;statistic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;without littering my code with a bunch of lines like&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:stats&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}}&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:stats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In other words, I want to be able to perform abitrarily deep assignment: for a hash &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h[:a][:b][:c] = 5&lt;/code&gt; should be just as valid as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h[:a] = 5&lt;/code&gt;, even if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h&lt;/code&gt; does not yet have the key &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:a&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is simple to implement by subclassing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash&lt;/code&gt; and overriding the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]&lt;/code&gt; operator:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FreeformHash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;has_key?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FreeformHash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Let’s see it in action!&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:009:0&amp;gt; h &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; FreeformHash.new
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt;
irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:010:0&amp;gt; h[:a][:b][:c] &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 5
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; 5
irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:011:0&amp;gt; h
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;:a&lt;span class=&quot;o&quot;&gt;=&amp;gt;{&lt;/span&gt;:b&lt;span class=&quot;o&quot;&gt;=&amp;gt;{&lt;/span&gt;:c&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;5&lt;span class=&quot;o&quot;&gt;}}}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;One downside is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]&lt;/code&gt; is now destructive, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h[key]&lt;/code&gt; will always create elements if they don’t already exist. &lt;a href=&quot;http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-has_key-3F&quot;&gt;Hash#has_key?&lt;/a&gt; should always be used where appropriate to avoid unwanted keys being created.&lt;/p&gt;

&lt;h3 id=&quot;default-values&quot;&gt;Default values&lt;/h3&gt;

&lt;p&gt;You might already know that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash.new(X)&lt;/code&gt; will create a hash that returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt; as a default value on a lookup for a nonexistent key:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:001:0&amp;gt; polite_hash &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; Hash.new&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;I'm so sorry&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt;
irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:002:0&amp;gt; polite_hash[&lt;span class=&quot;s1&quot;&gt;'not a key'&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;I'm so sorry&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is all well and good, but what if I want a default value that I can modify for each key? For instance, let’s say that I’m keeping track of some player statistics in a hash, and I want to be able to easily modify them without having to manually instantiate a hash for each player. My first thought is to specify a hash as a default value, as so:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:003:0&amp;gt; bad_hash &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; Hash.new&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt;:wins&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;0, :ties&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;0, :losses&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;0&lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt;
irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:004:0&amp;gt; bad_hash[:player1]
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;:count&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;0&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:005:0&amp;gt; bad_hash[:player1][:wins] +&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 1
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; 1
irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:006:0&amp;gt; bad_hash[:player2]
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;:wins&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;1, :ties&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;0, :losses&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;0&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Uh oh, what happened here? It turns out that the default value of a hash is a single object that is used for all default values, so changing it once will change it for all default values, with nothing being stored in the hash itself:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:007:0&amp;gt; bad_hash.default
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;:wins&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;1, :ties&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;0, :losses&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;0&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:008:0&amp;gt; bad_hash
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Clearly this is not the way to go.&lt;/p&gt;

&lt;p&gt;Fortunately, Ruby provides an alternative approach to default values: you can specify a block like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{|hash, key| do_stuff}&lt;/code&gt; as an argument instead, and that block will execute every time a lookup fails. While no element is added to the hash by default in this case, you can use a block of the form &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{|hash, key| hash[key] = object}&lt;/code&gt; to insert &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;key =&amp;gt; object&lt;/code&gt; into the hash on every failed lookup.&lt;/p&gt;

&lt;p&gt;A quick example:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:009:0&amp;gt; players &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; Hash.new&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;|h, k| h[k] &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;wins: 0, ties: 0, losses: 0&lt;span class=&quot;o&quot;&gt;}}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt;
irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:010:0&amp;gt; players[:alex][:wins] +&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 1
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; 1
irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:011:0&amp;gt; players[:other_player][:losses] +&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 1
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; 1
irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:012:0&amp;gt; players
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;:alex&lt;span class=&quot;o&quot;&gt;=&amp;gt;{&lt;/span&gt;:wins&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;1, :ties&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;0, :losses&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;0&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;, :other_player&lt;span class=&quot;o&quot;&gt;=&amp;gt;{&lt;/span&gt;:wins&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;0, :ties&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;0, :losses&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;1&lt;span class=&quot;o&quot;&gt;}}}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;deep-sort&quot;&gt;Deep sort&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://www.igvita.com/2009/02/04/ruby-19-internals-ordered-hash/&quot;&gt;As of Ruby 1.9&lt;/a&gt;, hashes preserve their order of keys, but there are no methods for reordering keys. What do we do if we want to sort the elements in a hash by key?&lt;/p&gt;

&lt;p&gt;Fortunately hashes include the &lt;a href=&quot;http://www.ruby-doc.org/core-1.9.3/Enumerable.html&quot;&gt;Enumerable&lt;/a&gt; module, and thus have a &lt;a href=&quot;http://www.ruby-doc.org/core-1.9.3/Enumerable.html#method-i-sort&quot;&gt;sort&lt;/a&gt; method, but that returns an array of pairs, not a hash. To sort a hash by key and return a hash, we need to do:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;where &lt;a href=&quot;http://www.ruby-doc.org/core-1.9.3/Hash.html#method-c-5B-5D&quot;&gt;Hash[]&lt;/a&gt; constructs a hash with given input, such as from an array.&lt;/p&gt;

&lt;p&gt;So far so good. Now, what if we have a hash like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{y: {b: 2, a: 3}, x: {d: 4, e: 5, c: 6}}&lt;/code&gt; and we want to sort at each level, getting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{x: {c: 6, d: 4, e: 5}, y: {a: 3, b: 2}}&lt;/code&gt; as a result?&lt;/p&gt;

&lt;p&gt;This can be done in one line with a recursive function:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Hash&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;deep_sort&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_a?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;deep_sort&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]}]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deep_sort&lt;/code&gt; works by first sorting the hash, then mapping over the elements of the hash, deep-sorting their values if possible, and finally converting the results back to a hash (because the mapping produces an array of pairs).&lt;/p&gt;

&lt;p&gt;Now let’s see it in action:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:006:0&amp;gt; unsorted_monstrosity &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;y: &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;b: 2, a: 3&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;, x: &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;d: 4, e: 5, c: 6&lt;span class=&quot;o&quot;&gt;}}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;:y&lt;span class=&quot;o&quot;&gt;=&amp;gt;{&lt;/span&gt;:b&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;2, :a&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;3&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;, :x&lt;span class=&quot;o&quot;&gt;=&amp;gt;{&lt;/span&gt;:d&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;4, :e&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;5, :c&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;6&lt;span class=&quot;o&quot;&gt;}}&lt;/span&gt;
irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:007:0&amp;gt; unsorted_monstrosity.deep_sort
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;:x&lt;span class=&quot;o&quot;&gt;=&amp;gt;{&lt;/span&gt;:c&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;6, :d&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;4, :e&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;5&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;, :y&lt;span class=&quot;o&quot;&gt;=&amp;gt;{&lt;/span&gt;:a&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;3, :b&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;2&lt;span class=&quot;o&quot;&gt;}}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And there we have it: three simple tricks that serve as useful examples of just how elegantly powerful Ruby can be.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Dynamic Pluralization with PHP and jQuery</title>
   
    <link href="http://alex.nisnevich.com/blog/2012/07/30/dynamic_pluralization.html"/>
   
   <updated>2012-07-30T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2012/07/30/dynamic_pluralization</id>
   
    <content type="html">&lt;p&gt;&lt;em&gt;(Note: I originally posted this on Facebook last summer, in my wild, PHP-loving days. While my toolbelt has changed since then, I still agree with the sentiment of the post, and so am reformatting it and reposting it here.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here’s a rather clean solution to dynamically pluralizing text that I’ve come up with. I wonder how other people deal with this.&lt;/p&gt;

&lt;h3 id=&quot;background&quot;&gt;Background&lt;/h3&gt;

&lt;p&gt;Web sites incorrectly using the plural form of nouns for dynamic content (e.g. “1 comments”) are a pet peeve of mine. Working on a site that displays lots of dynamic data that’s refreshed via AJAX calls made me want to come up with a clean way to deal with pluralization.&lt;/p&gt;

&lt;p&gt;Of course, it’s easy to write a PHP function like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pluralize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$plural&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$singular&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$singular&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$plural&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;?&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;and pass the current value of the data field in question. The necessity for client-side functions arises, however, when this data field dynamically changes (e.g. a user adds a comment and it’s automatically added to the page through AJAX), and then “1 comment” becomes “2 comment”. I couldn’t find a solution online that avoids especially messy JavaScript, so I came up with a couple of my own. Both approaches have the same general idea: a PHP &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pluralize()&lt;/code&gt; function is called to do initial pluralization and to create the HTML structure that can then be used by a JavaScript &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pluralize()&lt;/code&gt; function to refresh the pluralization whenever dynamic content is changed.&lt;/p&gt;

&lt;h3 id=&quot;the-simpler-solution&quot;&gt;The Simpler Solution&lt;/h3&gt;

&lt;p&gt;This approach modifies the simple function above to create a pair of “singular” and “plural” spans and simply hide the one that’s not used at the moment.&lt;/p&gt;

&lt;p&gt;PHP code for method #1:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pluralize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$plural&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$singular&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;
        &lt;span class=&quot;s1&quot;&gt;'&amp;lt;span class=&quot;singular'&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$id&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&quot;&amp;gt;'&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$singular&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&amp;lt;/span&amp;gt;'&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt; 
            &lt;span class=&quot;s1&quot;&gt;'&amp;lt;span class=&quot;plural'&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$id&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;' hidden&quot;&amp;gt;'&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$plural&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&amp;lt;/span&amp;gt;'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;s1&quot;&gt;'&amp;lt;span class=&quot;singular'&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$id&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;' hidden&quot;&amp;gt;'&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$singular&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&amp;lt;/span&amp;gt;'&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt; 
            &lt;span class=&quot;s1&quot;&gt;'&amp;lt;span class=&quot;plural'&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$id&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&quot;&amp;gt;'&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$plural&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&amp;lt;/span&amp;gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;?&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;JavaScript code for method #1:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pluralize&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.singular&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.plural&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.plural&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.singular&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;CSS for methods #1 and #2:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-css&quot; data-lang=&quot;css&quot;&gt;&lt;span class=&quot;nc&quot;&gt;.hidden&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To use this approach for a comment form like the one mentioned above, one would call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pluralize(&quot;comments&quot;, &quot;comment&quot;, $numComments)&lt;/code&gt; in the PHP, and then call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pluralize (value)&lt;/code&gt; in JavaScript whenever the number of comments changes.&lt;/p&gt;

&lt;p&gt;It’s a little messier to use this on a page with multiple dynamic fields (e.g. a reddit-style threaded discussion where every post can have “1 point” or &amp;gt;1 “points”), but still possible. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pluralize (value, id)&lt;/code&gt; function allows you to only modify the pluralization of one element at a time, as long as you pass a unique ID to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP pluralize()&lt;/code&gt; function calls.&lt;/p&gt;

&lt;p&gt;This works, but it’s a little ugly to be left with a collection of spans with id’s like “plural1”, “plural2”, “plural3”, etc. For my second approach, I tried to get rid of this by structuring each “pluralization block” with a consistent hierarchy.&lt;/p&gt;

&lt;h3 id=&quot;the-more-involved-solution&quot;&gt;The More Involved Solution&lt;/h3&gt;

&lt;p&gt;The idea here is that the PHP pluralize() function takes a single parameter $contents of the form:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$contents&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'plain'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'There'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'pluralize'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'are'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'is'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'value'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$attendees&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'pluralize'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'people'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'person'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'plain'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'attending!'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;and uses it to create an HTML hierarchy like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;pluralizer-container&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    There
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;singular hidden&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;is&lt;span class=&quot;nt&quot;&gt;&amp;lt;span&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;plural&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;are&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;42&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;singular hidden&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;person&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;plural&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;people&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    attending!
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The code for this is pretty straightforward:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pluralize&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$contents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&amp;lt;span class=&quot;pluralizer-container&quot;&amp;gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$contents&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;plain&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;' '&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;pluralize&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&amp;lt;span class=&quot;singular&quot;&amp;gt;'&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&amp;lt;/span&amp;gt; '&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&amp;lt;span class=&quot;plural&quot;&amp;gt;'&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&amp;lt;/span&amp;gt; '&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;nv&quot;&gt;$value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
                &lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&amp;lt;span class=&quot;value&quot;&amp;gt;'&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$value&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&amp;lt;/span&amp;gt; '&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&amp;lt;/span&amp;gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str_replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'class=&quot;plural&quot;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'class=&quot;plural hidden&quot;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str_replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'class=&quot;singular&quot;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'class=&quot;singular hidden&quot;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;?&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The new JavaScript function takes advantage of this consistent layout to correctly pluralize all elements on the page at once without requiring any parameters:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pluralize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.pluralizer-container&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.value&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.singular&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.plural&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.singular&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.plural&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As a result, the JavaScript side of things is much easier to use, at the expense of a more complicated PHP call. This approach seems more elegant to me, but it does have some drawbacks: it takes some work to create the array that describes each “pluralization block”, and the JavaScript function acts on all pluralization blocks every time it’s called, which is more inefficient, though I imagine the performance difference is negligible.&lt;/p&gt;

&lt;h3 id=&quot;to-wrap-up&quot;&gt;To Wrap Up&lt;/h3&gt;

&lt;p&gt;Correctly pluralizing dynamic data is not especially difficult, and can be accomplished in some 40 lines of code. The two approaches I outlined both work fine and mainly differ in whether one wants to have slightly more complicated calls to the server function or the client function.&lt;/p&gt;

&lt;p&gt;These approaches are easy to extend both to other languages (such as Russian, with its singular-dual-plural number system) and to other grammatical functions (such as present tense vs past tense for events). Another cool extension to this could be to integrate an automatic PHP pluralization function (I’ve seen a couple floating around online).&lt;/p&gt;

&lt;p&gt;For those of you who use the Yii framework, I’ve encapsulated this functionality in a Yii widget for extra convenience. You can get it here:
&lt;a href=&quot;http://www.yiiframework.com/extension/pluralizer/&quot;&gt;http://www.yiiframework.com/extension/pluralizer/&lt;/a&gt;&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>Refreshing Browsers over SSH</title>
   
    <link href="http://alex.nisnevich.com/blog/2012/07/25/browsers_and_ssh.html"/>
   
   <updated>2012-07-25T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2012/07/25/browsers_and_ssh</id>
   
    <content type="html">&lt;p&gt;Today I was faced with the challenge of having to refresh a Chrome browser window on a networked Ubuntu machine via SSH. (Admittedly, I could have walked over to the computer in question and hit F5, but where’s the fun in that?) This seemed like it would be simple, but I immediately ran into problems.&lt;/p&gt;

&lt;h3 id=&quot;opening-chrome&quot;&gt;Opening Chrome&lt;/h3&gt;

&lt;p&gt;My first thought was to see if Google Chrome’s &lt;a href=&quot;http://peter.sh/experiments/chromium-command-line-switches/&quot;&gt;command-line interface&lt;/a&gt; has any support for interacting with existing windows, but unfortunately it doesn’t. Oh well - if the browser is set to remember the last opened tabs, then it should just be a matter of closing and reopening the window: once I SSH in,&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;killall chrome
google-chrome &amp;amp; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;should be all it takes, right?&lt;/p&gt;

&lt;p&gt;Well, not quite:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;chromium-browser:3217&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;: Gtk-WARNING &lt;span class=&quot;k&quot;&gt;**&lt;/span&gt;: cannot open display:&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Hmm, Chrome refuses to start because it can’t find a display. Since we’re logged in remotely, we’ll need to manually set the display to be the one that is currently being used:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DISPLAY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;:0.0&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;After this, running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;google-chrome&lt;/code&gt; works. So far, so good.&lt;/p&gt;

&lt;p&gt;Now comes the next problem: the original window was full screen. Could I make the new instance be full screen as well?&lt;/p&gt;

&lt;h3 id=&quot;making-it-full-screen&quot;&gt;Making it full screen&lt;/h3&gt;

&lt;p&gt;Suprisingly, there is no command-line switch for full-screen mode! The closest I could find was &lt;a href=&quot;http://peter.sh/experiments/chromium-command-line-switches/#app&quot;&gt;–app&lt;/a&gt;, which didn’t seem to do anything. Well, if we can’t start the browser in full screen, can we at least make the window full screen after it starts?&lt;/p&gt;

&lt;p&gt;Here the &lt;a href=&quot;http://www.semicomplete.com/projects/xdotool/&quot;&gt;xdotool&lt;/a&gt; keyboard/mouse simulator package can come in handy - we can use it to mimic an F11 keypress as follows:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# You might need to sudo apt-get install xdotool&lt;/span&gt;
xdotool search chrome windowactivate &lt;span class=&quot;nt&quot;&gt;--sync&lt;/span&gt; key F11&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now all that’s left is the matter of timing: we should only run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xdotool&lt;/code&gt; once Chrome’s window has opened, so let’s give it a few seconds:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;2&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;putting-it-all-together&quot;&gt;Putting it all together&lt;/h3&gt;

&lt;p&gt;The full sequence of commands to run is:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DISPLAY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;:0.0
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;killall chrome
google-chrome &amp;amp; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;2
xdotool search chrome windowactivate &lt;span class=&quot;nt&quot;&gt;--sync&lt;/span&gt; key F11&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Since I don’t want to have to enter this every time I need to refresh the browser, I saved this into a shell file on the remote machine at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/local/bin/google-chrome-fullscreen&lt;/code&gt; and assigned appropriate permissions. Now, to refresh chrome on that machine, all I need to run is:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;ssh &amp;lt;host&amp;gt; &lt;span class=&quot;s1&quot;&gt;'google-chrome-fullscreen &amp;gt; /dev/null'&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;(The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt; /dev/null&lt;/code&gt; is necessary, apparently because otherwise the SSH session hangs while waiting for output.)&lt;/p&gt;

&lt;h3 id=&quot;what-about-other-browsers&quot;&gt;What about other browsers?&lt;/h3&gt;

&lt;p&gt;Similar method should work for other browsers. A generic browser-refresh script that works for me is:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# refresh_fullscreen&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DISPLAY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;:0.0
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;killall &lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt; &amp;amp; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;2
xdotool search &lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt; windowactivate &lt;span class=&quot;nt&quot;&gt;--sync&lt;/span&gt; key F11&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;where the usage is&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;refresh_fullscreen &amp;lt;browser name or path&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I’ve tried this with Firefox and Chromium and it seems to work fine, but your mileage may vary.&lt;/p&gt;
</content>
   
 </entry>
 
 <entry>
   <title>About this blog</title>
   
    <link href="http://alex.nisnevich.com/blog/2012/07/13/welcome.html"/>
   
   <updated>2012-07-13T00:00:00+00:00</updated>
   <id>http://alex.nisnevich.com/2012/07/13/welcome</id>
   
    <content type="html">&lt;p&gt;It’s been a while since I’ve had a blog, but I thought that it’d be nice to have a place to post projects,
ideas, tutorials, and whatever else is on my mind, programming-related or otherwise.&lt;/p&gt;

&lt;p&gt;I’ve only ever used Blogger before, so this time around, I’ve decided to do things differently. I wanted a
simple, self-hosted blogging system that would put me in control while still being mostly hassle-free. I think
I’ve found a winner in Github’s own &lt;a href=&quot;https://github.com/mojombo/jekyll&quot;&gt;Jekyll&lt;/a&gt;. After starting with
&lt;a href=&quot;https://github.com/krisb/jekyll-template&quot;&gt;krisb’s Jekyll template&lt;/a&gt; and fiddling around for a few hours, I’ve
found a whole host of things to love about Jekyll:&lt;/p&gt;

&lt;h3 id=&quot;why-use-jekyll&quot;&gt;Why Use Jekyll?&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;It’s quick to install with RubyGems: simply &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gem install jekyll rdiscount compass&lt;/code&gt; and you’re done!&lt;/li&gt;
  &lt;li&gt;The domain-specific language for page logic is simple and Rubyesque, and templates can be nested as in Rails.&lt;/li&gt;
  &lt;li&gt;I get to write blog posts in Markdown, which is much more convenient than HTML for text-heavy content.&lt;/li&gt;
  &lt;li&gt;Thanks to the custom Rakefile included with the template, building and deploying is as simple as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rake deploy&lt;/code&gt;,
and since the generated site is static, it can be hosted absolutely anywhere, with zero configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Admittedly, I’m not a big fan of the ERB-like layout files and would have preferred for them to be more like
Slim or HAML, but since I imagine Jekyll is designed for rather simple sites, markup-wise, I don’t see this
being too much of a problem.&lt;/p&gt;

&lt;h3 id=&quot;jekyll-and-subdirectories&quot;&gt;Jekyll and Subdirectories&lt;/h3&gt;

&lt;p&gt;A more pressing issue is that Jekyll doesn’t seem to allow blogs to exist in subdirectories of a domain. I’ve
experienced some issues trying to get this blog to run correctly on
&lt;em&gt;http://alex.nisnevich.com/blog&lt;/em&gt;, because the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{ { post.url } }&lt;/code&gt; values all treat
&lt;em&gt;http://alex.nisnevich.com&lt;/em&gt; as the root.&lt;/p&gt;

&lt;p&gt;A temporary solution for me has been to replace all instances of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{ { post.url } }&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/blog{ { post.url } }&lt;/code&gt;.
This works remotely, but breaks the site locally, because running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll --server&lt;/code&gt; will still try to serve the
blog at &lt;em&gt;localhost:4000&lt;/em&gt; rather than &lt;em&gt;localhost:4000/blog&lt;/em&gt; .&lt;/p&gt;

&lt;p&gt;I managed to circumvent this in the Rakefile via a rather hackish solution - an automatically generated symlink:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# (make_symlink is called from the build task)&lt;/span&gt;
&lt;span class=&quot;vi&quot;&gt;@dir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'blog'&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;make_symlink&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;sh&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'cd _site &amp;amp;&amp;amp; sudo ln -s . '&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@dir&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site/blog&lt;/code&gt; always points to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site&lt;/code&gt; locally, and all is well.&lt;/p&gt;

&lt;p&gt;Well, not really. This is still an ugly hack, and I’d like to look into more long-term solutions - perhaps I should give
&lt;a href=&quot;http://octopress.org/&quot;&gt;Octopress&lt;/a&gt; a shot.&lt;/p&gt;

&lt;h3 id=&quot;all-in-all&quot;&gt;All in All&lt;/h3&gt;

&lt;p&gt;Aside from the subdirectory issue, Jekyll does succeed in minimizing unnecessary distractions and letting me focus
on what I’m here for: the actual post content. Now all that’s left is for me to actually come up with that.&lt;/p&gt;
</content>
   
 </entry>
 

</feed>
