Buying a car: A text adventure game.

You are standing on the street. Before you there are three car dealerships, with a sign on top of each one. One says:
"MAZDA", one says "HYUNDAI", one says "SUBARU".

> Walk HYUNDAI

You walk into the Hyundai dealership.

> Look

You look around. There is a car and a car salesman.

> Look car

You move toward the car, hoping to look at it. The car salesman detects your presence and moves between you and the car.

"Hi there, I see you're looking for a car there. Got any questions, just ask me!" 

The salesman blocks your path.

> Ask Salesman.

The salesman doesn't know what you are asking.

> Ask salesman about car.

The salesman doesn't know anything about "car"

> Ask salesman about price.

The salesman doesn't know anything about "price"

> Ask salesman about car.

The salesman has no fricking clue as he is a complete idiot.

> Leave.

You leave the car dealership.

You are standing on the street. Before you there are three car dealerships, with a sign on top of each one. One says:
"MAZDA", one says "HYUNDAI", one says "SUBARU".

> Quit

You cannot quit. You must acquire transport.

I hate this game.

 

Dungeon.css: Dungeon Generator

I've been writing about Random Dungeon Generators over at wiki.secretGeek.net, and as a consequence, ended up putting a new random dungeon generator online.

Dungeon.css: Dungeon Generator

dungeon example

The layout of the maps is generated by a C# library, Karcero, by Oded Welgreen. A very nice library if you need to generate dungeons or mazes. (And why wouldn't you need that, hmm?)

The idea of the page I've put up is that you can choose a theme, and customize the background colors. Themes are entirely based on css. The maze is just a HTML table, with classes and styles.

Each theme is a style sheet, a pair of default colors, and a few gray scale pngs (with transparent backgrounds).

It's very easy to add a new style, or to improve an existing style.

Rock for Dungeon theme Door for Dungeon theme Wall for Dungeon theme Rock for Castle theme Door for Castle theme Wall for Castle theme Rock for Forest theme Floor for Forest theme Wall for Forest theme Rock for SpaceBase theme Floor for SpaceBase theme Door for SpaceBase theme

If I was really serious, I could've gone with SVG. Then *all* aspects of the color and scaling could be configurable through css. But it would also be trickier to author the pictures, for someone like me (with my MVP in "MS Paint").

The Cicada Principle

In the SpaceBase and Dungeon themes, I want a few randomly selected background tiles to use a different image, to give the rocks or space base a more natural feel. CSS doesn't currently include any kind of random function, so I used 'the cicada principle', whereby a set of nth-child rules with varying prime numbers will select a random-looking set of tiles. To wit:

tr:nth-child(13n)    td:nth-child(11n-10).rock { background-image:url(image/rock_geo2.png)}
tr:nth-child(7n-3)   td:nth-child(17n-12).rock { background-image:url(image/rock_geo2.png)}
tr:nth-child(17n)    td:nth-child(23n).rock { background-image:url(image/rock_geo2.png)}
tr:nth-child(7n)     td:nth-child(13n).rock { background-image:url(image/rock_geo2.png);}
tr:nth-child(19n-12) td:nth-child(21n).rock { background-image:url(image/rock_rare.png);}
tr:nth-child(5n)     td:nth-child(7n).rock { background-image:url(image/rock_geo.png);}
tr:nth-child(7n)     td:nth-child(5n).rock { background-image:url(image/rock_geo2.png);}
tr:nth-child(10n)    td:nth-child(10n).rock { background-image:url(image/rock.png);}

Finally

Note that depending on the parameters you choose, you can also generate complex, unthemed mazes.

complex_maze.png

Anyhow, the online dungeon generator is here, and the source code is available here.

 

Mercurial Workflow with Branching

I've written a bit about mercurial over the years, but never once mentioned branching. This is my basic guide to branching (I'm a basic kind of person) and it's based on a short email I wrote myself a long time ago, which I still occasionally rely upon. If you're unsure about anything, simply test it for yourself.

Our story begins, some happy day, when you are working very hard, in an existing repository. I don't know or care how you got the repository. Maybe you created it (with 'hg init') or you copied it (with 'hg clone').

Now, as you are working you realize that you want your current work to be put into a branch, to keep it out of harm's way.

Let's say you are working on a feature called WEAPONISE_SPACE, and you don't want to accidentally release it before the public have been warned. A branch is a nice place to perform your work, away from the boring, safe work the rest of your team are engaged in.

> hg branch WEAPONISE_SPACE
marked working directory as branch WEAPONISE_SPACE
(branches are permanent and global, did you want a bookmark?)

And obviously, if you are brave enough to weaponise space, you are brave enough to create branches that are permanent and global. If you do regret the 'hg branch {branchname}' command, then it can be immediately undone with 'hg branch -C' (or 'hg branch --clean').

The beautiful thing here is that you didn't need to create the branch before you started writing code. You just need to decide before your first commit.

Now when you commit, you will be committing on that branch:

> hg commit -m "Adding a small laser to the space shuttle"

When you try to push your work, let's see what happens...

> hg push
pushing to https://Nasa.gov/repo/shuttle
searching for changes
abort: push creates new remote branches: WEAPONISE_SPACE!
(use 'hg push --new-branch' to create new remote branches)

This is good... maybe you wanted to finish working on that feature before you pushed it? Mercurial won't let you accidentally push out a new branch. But it is an OK thing to do, if you want someone else to help you with your work, so let's use the new-branch option:

> hg push --new-branch
pushing to https://Nasa.gov/repo/shuttle
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 4 changes to 4 files

Now, your comrade Veronika is helping you weaponise space, as she is a specialist in weapon targeting systems.

She can see that the repository now has two heads (the default branch, which she last worked on, and your new branch)

> hg heads
changeset:   35:099848c53b6c
branch:      WEAPONISE_SPACE
tag:         tip
user:        laserKid
date:        Thu Sep 17 16:19:44 2015 +1000
summary:     further improvements to laser

changeset:   34:c836214f7238
user:        Veronika
date:        Wed Sep 16 17:04:16 2015 +1000
summary:     initial

She gets all changes:

> hg pull
pulling from https://Nasa.gov/repo/shuttle
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads)

Now Veronika has your code on her machine, but not in her working folder, as she hasn't 'update'd yet. If she's unsure what branch she's currently on, she can easily check:

> hg branch
default

She's still on default. If you haven't branched, then you are in the "default" branch.

How do we switch to the branch of our choice? We update to it...

> hg up WEAPONISE_SPACE
4 files updated, 0 files merged, 0 files removed, 0 files unresolved

If she had some local uncommitted work (or uncommitted merges) she would've gotten one of these two error messages:

abort: crosses branches (merge branches or use --clean to discard changes)
abort: outstanding uncommitted merges

...and she would've needed to commit her current work to default (or revert it)(or commit it to a different new branch, I guess), before proceeding.

Now, she's updated to the WEAPONISE_SPACE branch, she can work on it, commit her changes, and push:

> hg commit -m "Added a targeting system to LaserKid's laser."
> hg push

Back on your machine, you can pull Veronika's changes and update your local files.

> hg pull
...
> hg up
...

The manager rushes into the room and tells you that global tensions have increased and we must weaponise space TODAY! There's no time to inform the public, or test the lasers! You must ship the code as is! So, with a heavy heart, you close the branch, using a commit just for this purpose. (This is an optional step... but good repo hygiene! Any subsequent work on that branch will automatically cause it to reopen.)

> hg commit -m 'Finished Weaponising Space!' --close-branch

Now you want to merge that feature into the default branch. First, switch back to the default branch. Then merge the feature branch in. After that you can commit the merge and push it to the world.

> hg up default
...
> hg merge WEAPONISE_SPACE
4 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
> hg commit -m "Space... get ready for Weapons!"
> hg push
pushing to https://Nasa.gov/..

The rest, of course, is written in the history books. The targeting systems were good, but the laser intensity was poorly calibrated. Two continents were destroyed in the ensuing war.

Live fast, die young, leave a well-maintained repo history.

For extra credit....

When pushing, *only* push the current branch (the branch you are working on)

hg push
> hg push -b .

So, when pushing a new branch...

hg push
> hg push -b . --new-branch

When committing, include the A option (short for '--addremove') to ensure you're adding any new files.

hg commit -m "My commit message"
> hg commit -Am "My commit message"

(Make sure your .hgignore file is fully fleshed out, so you are not committing new files that don't belong in the repository)

For fullest extra credit... when pulling in other people's changes, if you want to merge them with your local changes, without creating spurious extra commits, use the --rebase option (requires the rebase extension in your mercurial config)

> hg pull -b . --rebase

To enable the rebase extension, you need to... actually, I've written all about that in this guide to mercurial extensions so just go there.

Summary/Cheatsheet

Command Comment
hg branch Check which branch you're working on
hg branch WEAPONISE_SPACE Create a branch
hg push -b . --new-branch Push only the current branch (which happens to be new)
hg push -b . Push only the current branch
hg heads See if there are any open "heads" (e.g. open branches)
hg pull -b . --rebase Pull just the current branch
hg pull -b WEAPONISE_SPACE --rebase Pull only the contents of a named branch (and merge any local commits you've already made)
hg up WEAPONISE_SPACE Switch to a named branch. So this will update the current working folder to contain the work from the named branch.
hg commit -Am "committing here!" Commit to the current branch, while addremoving files
hg commit -m 'Finished Weaponising Space!' --close-branch Close the current branch (good hygiene)
hg up default Update so that the current branch is now the default branch
hg merge WEAPONISE_SPACE Merge a named branch into the current branch

External Links

See Also

 

Fixing Keyboard Functionality on MacBook Air after Upgrading to Windows 10.

Full title: "Fixing Function Key Support (pageup, pagedown, home, end, print screen, delete, etc.) on MacBook Air running Boot Camp, after Upgrading to Windows 10.

I upgraded my MacBook Air from Windows 8.1 to Windows 10. Everything seemed good. A little *too* good. Even the trackpad seemed to work. Then I realised there was one terrible terrible problem. The 'fn' key had stopped working. That handy dandy little sucker that unlocks the power of Page Up, Page Down and so on -- was now just a useless obsolete chunk of good for nothing.

I soon worked out the solution, and since I don't see it documented elsewhere on the inkernets, I'd better publish the deets myself.

  1. Download the latest Bootcamp files, from here: Boot Camp Support Software 5.1.5640
  2. Unzip the file
  3. In the resulting folder: "\BootCamp5.1.5640\BootCamp\Drivers\Apple", find "AppleKeyboardInstaller64.exe".
  4. Install that.
  5. Reboot.
  6. Use Page Up, Page Down and so on, all day long.
 

The Spectator Sport of Vendor Topping

Salesmen representing five different software vendors shuffled nervously into the room, viewing one another with thinly veiled enmity. They'd each received my meeting invite with details of the solution required, and a few choice hints that our budget was in the millions, but didn't realize they'd be pitching at the same time as one another.

The first vendor approached the white-board and nervously drew a simple square.

an_application.png

He explained that his application completely fulfills *all* of the requirements.

The next vendor immediately interrupted and drew a series of boxes next to that box.

a_series_of_applications.png

He explained that his *series* of applications solves not just today's problems but anticipates tomorrow's challenges.

Before he said another word, the next vendor took a pen and drew a box right around all of the existing boxes.

an_application_suite.png

He explained that his solution is not just a series of applications, but a *suite* of application that perform seamlessly together in an orchestrated...

Before he could finish the next vendor had drawn a box under that box.

a_platform_offering.png

He explained that his suite of applications are part of a *platform*. The platform provides *robustness* that the others lack.

Before he could continue the next vendor seized a white-board marker and drew a big box around the outside of all the other boxes.

a_managed_platform.png

He explained that his suite of applications come with a platform that is part of a fully *managed* service, with SLAs and compliance that other vendors can't hope to match.

By this time, all hell had broken loose and the vendors were openly brawling in the middle of the room. Arms, legs, fists and white-board markers were flying everywhere. One vendor was slamming another vendor's head against the floor and another was being forced to eat a whitepaper.

The fighting continued for several minutes before a security guard, Kev, entered the room, asked all of the vendors to leave and then he asked me how I managed to infiltrate the building and the meeting-room booking system, at a company I left five years ago.

My hobby? Physical infiltration. Watching software vendors try to top each other is just an added bonus. Kevin and I wiped the white-board clean for the 3rd time that week.

 

Which Uri Encoding method should i use in C#/.net?

This too is one of the boring "factual" posts. Sorry Lachlan.

I never know which .net uri encoding (or url encoding?) method to use in any given scenario.

So I've built this informative lookup table you can use, whenever you're wondering what sort of encoding to apply to a string that is part of, or all of, a URL. Or URI, url, Url, Uri, or uri.

(This is exactly the sort of fun stuff you need to think about constantly when writing a brilliant and much-loved tool like NimbleText, so that hopefully, sometimes, just sometimes less other people have to worry about it.)

uri_encoding.png

↑ That was a picture, which you can download and print out. ↓ This is a HTML table which you can download, view source and edit.

Character Url.Encode(s) HttpUtility.UrlEncode(s) HttpUtility.UrlPathEncode(s) Uri.EscapeDataString(s) Uri.EscapeUriString(s)
{space} + + %20 %20 %20
{tab} %09 %09 %09 %09 %09
{\r} %0d %0d %0d %0D %0D
{\n} %0a %0a %0a %0A %0A
{\0} %00 %00 %00 %00 %00
~ %7e %7e ~ ~ ~
! ! ! ! ! !
@ %40 %40 @ %40 @
# %23 %23 # %23 #
$ %24 %24 $ %24 $
% %25 %25 % %25 %25
^ %5e %5e ^ %5E %5E
& %26 %26 & %26 &
* * * * * *
( ( ( ( ( (
) ) ) ) ) )
_ _ _ _ _ _
+ %2b %2b + %2B +
{ %7b %7b { %7B %7B
} %7d %7d } %7D %7D
| %7c %7c | %7C %7C
: %3a %3a : %3A :
" %22 %22 " %22 %22
< %3c %3c < %3C %3C
> %3e %3e > %3E %3E
? %3f %3f ? %3F ?
` %60 %60 ` %60 %60
[ %5b %5b [ %5B %5B
] %5d %5d ] %5D %5D
\ %5c %5c \ %5C %5C
; %3b %3b ; %3B ;
' %27 %27 ' ' '
, %2c %2c , %2C ,
. . . . . .
/ %2f %2f / %2F /
' %27 %27 ' ' '

Remember kids: Cool URIs don't change. People do.

 

In search of brutal honesty

As part of the research I'm doing for the book I'm writing (Your First Product) I've been tinkering with google forms, and different ways of scooping ideas out of your customer's head.

To that end, I built this form which I would like you to fill out:

If that didn't work, you can visit it here:

Please provide some honest, anonymous feedback.

I shared the form via twitter a few hours ago, and already have received from some pretty "interesting" feedback. Mostly people seem to give the same sort of feedback they would normally give, but with more swear words than normal.

If you do have any feedback to offer, it is very much appreciated.

 

Writing Your First Mercurial Extension

I wanted to write an extension for mercurial, and after extensive googling, decided that there was no suitable starting point for an absolute beginner like me.

By absolute beginner I mean any person who's never messed with mercurial configuration before and never written any python before. It's strange that such people are not catered for, because most people on the planet have never messed with mercurial configuration before and have never written any python before. Even Shakespeare and Elvis have this particular attribute in common.

(The often cited 'Writing Extensions' only begins to be comprehensible once you've mastered all the concepts involved.)

So here's my absolute beginner's 3 minute guide, pitched at me and possibly you.

To tell mercurial about an extension, you edit your mercurial.ini file (or on non-windows machines, your '.hgrc' file.)

Your mercurial.ini file is located here:

%userprofile%\mercurial.ini

(If you don't have a file at '%userprofile%\mercurial.ini' then create one now.)

(In powershell, you would edit '$env:userprofile\mercurial.ini')

Now look for the extensions section, i.e. the section headed "[extensions]". (And add one if you need to)

And tell it about your extension. Like so:

[extensions]
helloworld =

It looks like we're setting helloworld to nothing. But we're not.

What we've done there is we've said "Hey mercurial, old buddy, if you can find an extension called 'helloworld', then I want you to enable it."

Naturally this isn't going to work, because mercurial won't be able to find just such an extension. But that technique is good enough for all of the built-in extensions (like 'color' and 'fetch').

At this point if we run the command:

hg help extensions

We'll get the result:

*** failed to import extension helloworld: No module named helloworld

(And a lot of other information as well.)

Very well then, let's give mercurial a little more info, so it can find our new extension.

[extensions]
helloworld = C:\Program Files\TortoiseHg\hgext\helloworld.py

That's neat... but there's no such file. Running 'hg help extensions' at this point will, quite predictably result in:

*** failed to import extension helloworld from C:\Program Files\TortoiseHg\hgext\helloworld.py: [Errno 2] No such file or
directory

...and we'll go right ahead and create the file 'helloworld.py'. Let's just create an empty text file in the right place. (You may need admin rights to create a file in that location.)

Now when we type hg help extensions we'll see the details of all the extensions, and in the section titled 'enabled extensions' it should say:

   helloworld   (no help text available)

Fine. So the next step is to add some help text to our extension.

Go to the empty file and add a 'doc string'. You do this by enclosing a statement in triple-quotes, like this """

"""hello world is a very simple extension
"""

(Python is white-space sensitive, so make sure there's no whitespace before any of those triple quotes)

Now we have our simple extension that does nothing, it just exists and broadcasts its existence.

When we type 'hg help extensions', we'll see the following in the 'enabled extensions' section:

  helloworld    helloworld is a very simple extension

And, even better, if you type:

hg help helloworld

you'll see...

C:\>hg help helloworld
helloworld extension - helloworld is a very simple extension

no commands defined

Ah ha! No commands defined? So our new mission is to create a "command" within our extension!

Here's a very simple command called 'hello'. And it introduces a few other concepts I'll describe in a moment.

"""helloworld is a very simple extension
"""
from mercurial import commands, extensions, util

def hello(ui):
    """say a big hello.
    """
    ui.write('well hello there world.\n\n')

cmdtable = {'hello':(hello,[],'hg hello says hello')}

commands.norepo += ' hello'

In order to tell mercurial about our command, I had to create a list called 'cmdtable' and put my command, 'hello' in it, plus a few details about ways to call it.

I also needed to tell mercurial that this particular command should work just fine even if it is called when there is no repository present. (Thus, commands.norepo += ' hello')

So what have we achieved so far?

now i can type:

hg help extensions

-- and see that my extension exists. and i can type

hg help helloworld

-- and see help for my extension, including details of its commands.

And i can type

hg help hello

-- and get help on its one command.

Finally -- and most importantly, I can type:

hg hello

and have it run my new command.

Now there was a little hint given when I typed hg help hello. The message mercurial gave me was:

list of commands:

 hello    say a big hello.

use "hg -v help helloworld" to show builtin aliases and global options

And that just about wraps it up for a complete beginner. We've reached the hello world stage.

Notice the enticing message mentions builtin aliases and global options. That's what you can look into next, if you really want to achieve some Next Level Mastery of Mercurial Extenions. But for now we're done.

Further reading

 

You Feel The Blood Drain From Your Face (A DevOps story)

I just had a flashback that might be worth sharing.

Over 10 years ago. I was working as a contractor at the head office of a company that had 300 field offices. Every office had its own database. Same schema, same tables, different data.

To help people in head office query the field offices, I'd built "Multi-Query". It was a fairly simple VB.net app, basically a clone of SQL Query Analyzer but with a checked listbox that let you select which field offices (plural) you wanted to execute the query against. Hence you could run a single query against 1 or more databases, as many as 300. The results were accumulated and tabulated for you.

I'd initially built "Multi-Query" for myself, but it was useful enough that some of the other analysts asked for a copy. We were under the pump (always) and this tool would let us get the results we needed. But it was extremely rough, ugly, and error-prone. Back then we'd call it a "foot-gun". Today we'd call it DevOps.

multi_query.png

Some of the other analysts were not particularly versed in writing SQL queries, so I also did a little mentoring about joins and NULL handling and the like.

One day, I'm sitting there working hard, when I heard one of them say "Uh-oh."

At this point I was not alarmed. I sat across from this person (no cubicle divider, just a desk that used to be a lunch table).

Then I heard him ask a question. The words seemed to echo, like they were floating to me in a dream.

"Is there a way that you can put a query into a transaction, and roll it back, after you've run a delete?"

Echo. Echo. You Feel The Blood Drain From Your Face.

"Is there a way that you can put a query into a transaction, and roll it back, after you've run a delete?"

"Is there
     a way
      that you
       can put
         a query
         into
        a transaction,
      and roll
     (roll)
    (roll)

      it back,
     (back)
    (back)

   after
    you've run
    (run)
  (run)

    a delete?"
  (delete)
 (delete)

"After?" I said. "After you've?" My mouth was dry. I stood up. My legs were like jelly. I walked around the desk, a journey of five steps that seemed to take forever. 300 offices. A single delete query. 300 offices. Delete queries run by people who don't know how databases work. The potential for devastation was limitless.

"What. Did. You. Do? (Do) (Do)"

But no. It wasn't you. It was me.

I put the tools in your hands. I gave you the weapon. I handed you the ammunition.

"What did I do?"

Queries don't delete databases, people do.

Lucky for me: it was nothing. It was a single row of config data in a single office.

It was NOTHING. But it was also a hell of a lucky wakeup call.

The new version, issued minutes later, refused to run any query that contained a "delete" or a "truncate" or a "drop" and it made a noise if you tried. (Not a perfect defense, but a start)

What mattered most was that we took control of a test environment, used it to check our logic first. I needed to lead by example, not lead by jangling my spurs and rushing fearlessly into production every 5 minutes.

Slow down. Be careful out there.

 

UPDATE a table using a JOIN to another table (or to itself)

Soooooooooo, this is going to be one of those boring SQL-Server posts.

Despite writing T-SQL day in day out since forever, I often forget the syntax for an UPDATE statement that JOINS with another table (possibly itself). So here it is:

UPDATE
    p
SET
    p.ManagerEmail = m.Email
FROM
    Person p
INNER JOIN
    Person m
ON
    p.ManagerID = m.ID

Points to remember are:

The first part, 'UPDATE X' is simply 'UPDATE' followed by the alias of the table (you don't need to say the table's name there)

And (contrary to what some internet randos will tell you) you don't need to add a where clause to stop the update from applying to all rows of the table. It will only apply to the rows which are matched by the join condition used. (For an inner join at least ;-) )

I was using TimeSnapper to play back my work when I saw myself struggling with this query. And I remembered seeing myself struggle with the exact same thing another time (also via playback). So I'm putting this here, so that the act of typing it out might help cement the entire thing in my mind.

Also, I find it amusing that if you to do this in MySQL you basically write all the same things in a completely different order. So I'll include a MySQL example here for reference, too.

UPDATE
    Person p
INNER JOIN
    Person m
ON
    p.ManagerID = m.ID
SET
    p.ManagerEmail = m.Email