Mon 31 March 2025

Being A Lurker

Since the release of ChatGPT searches that include the term 'reddit' have doubled. Meanwhile interest in Substack grew four-fold during the same period. We are living in a time where content created by people is becoming more valuable and the best content is moving behind a paywall.

It's becoming easier to stand out. The information on the internet is converging to an average as the same SEO tactics are being applied to every webpage, marketing teams pay for the privilege of appearing at the top of search results or there's a constant need to churn out content in fear of losing relevance. People are hungrier than ever for an authentic and relatable tone of voice.

It's time to stop caring about what people think. The sooner you do so the better, and a few other reasons as to why you should start putting yourself out there.

People want to make connections

At one point there were at least three tech meet-ups a week in London. They are all similar, it's a good place to go to get some food when you're penny pinching. You come away from these conferences with a full belly and some esoteric knowledge from the presentations, however one thing I've come to realise is that these tech gatherings aren't very conducive to networking.

At first I thought it may be the type of persona these caverns attract, however I have come to realise that being the most outgoing person in the room was the wrong way to approach getting to know people.

Instead of making an effort to introduce yourself one person at a time you can save a lot of this hassle by booking a slot on stage and introducing yourself to everyone at the same time. Your name will be on the program, so those that forget can look you up. You're also forced to give 5 mins of content to everyone in the room.

(Wait for my next lightning talk, where I try not make it passed the introduction).

After your presentation you'll find that people start introducing themselves to you and you'll get away from actively trying to locate the like-minded individuals. Your talk also acts like a filter, so you might get people approaching that are interested in what you had said.

Improving and feedback

You might not like every follow up or the opinions others have. Putting something out there in the universe enables others to interact with it. It opens you up to feedback. Which is a great conductor to learning. We have modern physics due to trial and error, and we can probably thank trial and error for #npc #ganggang Either way, it's about discovering what works.

In software we learn that it's best to have tight feedback loops. The sooner we know we are doing the wrong thing the better. This saves us working on something for 6 months which ends up being a dead end. Some of my most memorable 'Ah-ha' moments have been from feedback. Important feedback sticks.

The easiest way to launch bad software is to shut yourself out to feedback. The same can be said about a skill, knowledge or an idea. You don't know if you're decent at anything unless you've tried it. If you think you're good at something, prove it.

Why I Write

There are about ten to twelve notebooks of thoughts I've accumulated over my working years. I'm sure each idea, in those books, is amazing and I can live pretending that's the truth or I can put some of them into writing and learn where they're flawed or where my writing is flawed.

If I wanted my writing to be terrible, the best way I could do this; is to avoid writing at all costs.

At the moment it's like seeing someone that doesn't look like they workout; in the gym. They've probably already acknowledged that they've been programming for the last 8 years but at least they're doing something about it.

There are a lot of half baked product ideas out there that have never seen the light of day and I'm sure they're amazing in the minds of the creator, but nothing will bring them down to earth quicker than getting user feedback. There might be one key piece of feedback between them and making something amazing, what ever thought is stopping you from getting here is probably silly in comparison.

There's also the benefit of going over something half baked with a fine comb that allows an idea to develop. It also helps the concept sink in.

Start being uncomfortable

I've convinced myself that we learn the most from being uncomfortable.

There's a reason we call something a comfort zone. It's the space that you operate in that is most familiar to you. So by definition the unfamiliar is probably a space you know least about. Diving into the unknown and getting a little uncomfortable can help you grow and learn. You might not be as bad as you thought you were, and if you're rubbish you're still a mile ahead of all the lurkers.

It's mostly crap on average.

S Williams-Wynn at 12:13| Comments()

Mon 24 March 2025

Travel Planning

I've recently come back from a career break. During which I travelled around Asia with my fiance. Planning such a long trip can be quite daunting at first. So here's how I did it.

We were in Asia for 5 months, but I'd say planning a trip that's 1 or 2 months long is probably the same as planning a trip that's 5 months long. (With the exception of needing to also plan around seasonal weather). The first thing to figure out is how much you're willing to spend.

Total Spend

Your total spend should include flights, cost of taxis, and meals and nights outs. As an example let's imagine the most we are willing to spend on a two month trip (if we were to really dish out) is about ~£9,000. Remember this is a two person trip. So given that we can spend £9,000 on this trip how long can we go for?

Cost Per Month

It's roughly estimated from rent £1,400-£2,000 and monthly living expenses ~£1,500. Plus a return flight on the high end ~£1,000.

For a long trip you only need one flight, so you can average that cost of travel across the two months. In our case the largest flight cost was to Japan from London and back. Looking at it now I see this cost us £1087.

This is where we would divide the budget across the two months; giving us a rough monthly spend of £3,500. If we are going for two months that gives us £2,000 on flights.

Cost Per Night

We need to keep in mind how many days are in each month, March is 31 days and April is 30 days. We break our budget into a table and set a goal of spending £1,200 a month on accommodation.

month max days npd
March £1,200 31 £38.7
April £1,200 30 £40.0

From the table above you can notice that our cost per night for the hotel is actually slightly higher in April because it's one day less than March.

Now there's a goal to aim for when booking hotels. We booked all the hotels 6 months in advance. In some cases the hotels hadn't setup their availability in their calendar system since we were thinking further ahead than they were.

We would look up the price of the hotel again when we were at the hotel and found that in cases we saved money, we saved £50-£200. And when we overpaid we overpaid by £2-8. So overall advance bookings turned out well for us.

The actually cost for us per night in Japan ended up being £55.21 in March. (We did go skiing for 2 weeks) and £47.13 in April So we blew the budget but the following month we spent £33.01 and then £30.02 so we clawed it back. There's also no reason to say you can't spend a little bit more on accommodation but this just means you'll need to spend less on other things like meals and capsule toys.

Because our spend was £55 this meant our "food/other" budget was about £61 per day. Also keep in mind I'm trying on purpose to over budget. Having this £61 concrete meant I could keep a rough idea of how much we should cap our spend each day. £61 does end up being quite a bit of money, especially if you're making breakfasts at home and you're not eating at fancy restaurants every night.

But we also have to note that that £61 also includes metro/bus tickets, souvenirs and entrance to theatres/museums. This forced us to diversify what experiences we were having. Walking and exploring Tokyo is more affordable than seeing kabuki every night.

Tracking

Keeping track of all the costs is really important. Consider how many hotels and flights we had to book. I also had to make sure every night was accounted for so we weren't left scrabbling for a place to stay at the last minute.

I had two spreadsheets for this. The first was a "per day" accommodation spend where I listed the months of travel at the top and the day of the month on the left.

day March April
1 35.1 42.18
2 42.18
3 42.18

This way I knew that, if there were gaps in this table, there were nights that were not accounted for. E.g. In the table above I need to book a hotel for the night of the 2nd and 3rd.

My second spreadsheet kept track of realised costs. A large table containing everything I've paid for, the date I paid, the number of nights, the start and end. In some cases we had to pay on arrival. I had a column which indicated if it was accommodation or travel and lastly notes which indicated if we should expect breakfast or dinner or both. So I'd make a payment, I'd then fill in the "per day" table so I knew where we were going to be and when.

In some cases we needed to note down on a certain day that we should prepare food the night before since we were catching a train which would not include a lunch and you'll want to avoid having to buy food at a station or while on a train as these will have an added margin on the cost that I wanted to avoid.

Conclusion

Planning a big trip can seem quite overwhelming at first, but once you break it down and you know where you're likely to be it's a matter of filling in the pieces. We started with Skiing then went to Tokyo and then did the Nakasendō trail between Kyoto and Tokyo. Making our trip as adventurous as we can. We gave ourself generous amounts of time to get across the town so we never missed a flight or train. It's better to need to waste time exploring shops and stations than miss a train you'd paid for.

We had the whole trip sorted out by the time we set foot in Japan so from then it was just a matter of enjoying ourselves and following the little reminders we'd set 6 months ago.

I'd say the best part was being able to forget what we'd booked half a year prior since it made for a pleasant surprise when we arrived and I had a reminder saying "the accommodation is taking care of breakfast and dinner so don't worry about finding food."

S Williams-Wynn at 12:56| Comments()

Mon 17 March 2025

Chess Programming Part 1

While exploring an interesting game engine which compiles python code to WASM; I thought, there's no better way to have fun than to write a chess engine. This was the start of a one month long project which led me to create my own chess AI.

Chess programming is a topic that has been written about extensively, it has been a proving ground for some of the first AI researchers and even today it is still used as a benchmark for techniques in applied research.

This article is the first part in a series of three in which I attempt to distill some insight into the application of algorithms in software, namely chess programming. In the hopes that it may inspire how you approach solving similar problems in the future.

Bit Operations

If you've ever done some leetcoding or any other live algorithm style interview prep in software engineering you might have come across the odd question on bit operations and wondered if you'll ever need to actually do these in your day to day job. I'm not here to answer that, but I am here to show you how it applies to chess programming.

Firstly what are bit operations:

a bitwise operation operates on a bit string, a bit array or a binary numeral at the level of its individual bits. It is a fast and simple action, basic to the higher-level arithmetic operations and directly supported by the processor.

Wikipedia

Essentially bitwise operations are physical operations that are embedded into the processor, they consist of your logical AND, OR, XOR and bit shifts. The mathematical operations that you're familiar with (such as Add, Subtract etc.) are all built upon bitwise operations.

Here's an example of a NOT operation:

NOT 0111  (decimal 7)
  = 1000  (decimal 8)

And an example of an AND operation:

    1010 (decimal 5)
AND 1100 (decimal 12)
  = 1000 (decimal 8)

We can see these operations supported in python:

>>> 0b1100 & 0b1010
8
>>> bin(0b1100 & 0b1010)
'0b1000'

There's two ways we can shift bits. (Left and right) Here's an example in python:

>>> # Left
>>> bin(0b0010 << 1)
'0b100'

>>> # Right
>>> bin(0b0010 >> 1)
'0b1'

Explaining how the processor does addition is out of scope of this blog post, but put together some of these bit operators and you've got yourself a function that can do addition.

Bitboards

Back to the chess:

We are doing all of this essentially to get as close to the processor as possible, since we want the most performance out of a computer as we can by avoiding any unnecessary operations.

In chess programming we will use a single number to keep track of all the positions of pieces on the board. This is what we call a "bitboard". We will have a few bitboards to maintain the game's state. The first one will be 'board' which tracks all the pieces. At the start of the game this number will be: 0xFFFF00000000FFFF. Which is hexadecimal for 18446462598732906495. If we print the binary form of this number and format it over 8 rows we get the following:

 Bitboard:

8  1 1 1 1 1 1 1 1
7  1 1 1 1 1 1 1 1
6  0 0 0 0 0 0 0 0
5  0 0 0 0 0 0 0 0
4  0 0 0 0 0 0 0 0
3  0 0 0 0 0 0 0 0
2  1 1 1 1 1 1 1 1
1  1 1 1 1 1 1 1 1
   a b c d e f g h

There are a few other bitboards to track during a game of chess. There will be 2 boards for each colour (white and black). And there will be a bitboard for each type (6). Which gives us a total of 9 bitboards, or 9 numbers to cover the entire game state of chess. Here's two more examples of bitboards, one for pawns and one for white pieces:

>>> pawns = 0x00FF00000000FF00
>>> white = 0x000000000000FFFF

Now we can uses these three numbers to give us the answer to some questions for example; Where are all the white pawns?

>>> board & pawns & white
65280

 Bitboard (65280):

8  0 0 0 0 0 0 0 0
7  0 0 0 0 0 0 0 0
6  0 0 0 0 0 0 0 0
5  0 0 0 0 0 0 0 0
4  0 0 0 0 0 0 0 0
3  0 0 0 0 0 0 0 0
2  1 1 1 1 1 1 1 1
1  0 0 0 0 0 0 0 0
   a b c d e f g h

During a game of chess you'll use these to calculate which pieces are under attack. (e.g. given: knightsmove & theirpieces the result will be 0 if there's no piece under threat). We can also use these bitboards workout where the valid moves are with moves & ~board. (Moves and not board).

A chess AI that's looking 4 moves ahead will have ~5mil positions to consider, so having these operations done on a low level can help boost the performance of the software. The more you squeeze out of performance the more accurate your chess AI will be, because this improves it's ability to look into the future.

Beyond Chess

In summary learning about how bitboards are applied to chess programming has allowed me to understand how bit operations can be applied practically in software. Before I dived into learning how chess is programmed I had only imagined keeping track of the board state using several objects and an array, however I now see there's more than one way to crack an egg; the board state egg.

I hope the article helps to providing insight into practical applications of bit operators and leaves them to be considered as valuable for solving technical problems or at least reminds software engineers that we have them in our toolbelt.

Further Reading

S Williams-Wynn at 21:34| Comments()

Mon 10 March 2025

Flash Cards

A while back I found out that Jnr. Doctors are keen on using flash cards to help them revise for exams. It occurred to me that I could also use flash cards to drill some of the concepts that would come up in discussions about software systems and system design.

At the time I was developing on a raspberry-pi with a 7-inch screen and I was limited to how much I could install on the little SD card. At first I tried to use a browser based flash card tool, but I was in the mood for doing some deep dives to understanding how these actually work under the hood.

This led me to develop Kanki a bespoke flash card tool for the command line. In this blog post I break down the process I typically use to undertake a software project of this scale (i.e tiny scale).

Requirements

Like all good software projects we start with the high level expectations. What do we want this system to do? (Some of these are quoted straight out of my notebook.

  1. I want to have 1000+ flashcards based on software and system design
  2. I should be able to hide the answer, I should be able to reveal the answer.
  3. Can I input "correct", "close", "wrong" etc and update an SR algorithm to handle recall.
  4. Nice to have, filtering by topic.

In order to understand how flash cards operate you'd need to dig into figuring how the SR works:

Spaced Repetition: Newly introduced and more difficult flashcards are shown more frequently, while older and less difficult flashcards are shown less frequently in order to exploit the psychological spacing effect. The use of spaced repetition has been proven to increase the rate of learning. - Wikipedia

Entities

We need to know what things we will be working with:

Card
- Question (string)
- Answer (string)
- Topic (string)
- DeckID (int)

The card is the question and answer entity which I might have 1000 of (In the end I think I had about 60).

Deck
- Id (int)
- Name (string

The deck is the entity which groups cards, so a card belongs to a deck and this allows me to have cards around separate topics.


Interface

This isn't far off from what I drew in the notebook but clearly I had a vision and the following sort of captures it at the time:

I want to have a view which allows me to load a specified deck. Create a new deck or add cards to an existing deck.

Load: -> deck_a, deck_b
New: (deck)
Add: (cards)

I want to display the different inputs I can provide as answers to each card.

Again,  Hard,    Good,    Easy
< 1min  < 6min   < 10min  4 days

These are some of the settings I'd like to fine tune as I get used to playing with the flashcard system.

config:
new cards / day
review cards / day

Spaced Repetition Algorithm

This is where things get slightly more involved since I had to understand how Anki and other flash card apps applied repetition. It was mostly finding the one that suited me best and felt the most natural to play with.

This is the algorithm I went for:

step 1: Cards have a queue field (set to 1 for 'learning') and a due field (in minutes) determining when a card should be shown again.

step 2: review cards, already studied but are now due to be relearnt. queue field set to (2) and due less than or equal to today.

step 3: 'new', queue is set to (0), these new cards are capped.

Essentially this determines how we populate the flash cards during a session. Using the three queue types:

  • 0: new
  • 1: learning
  • 2: review
  • 3: relearning (This is essentially a card deemed as "learnt" but you've forgotten it and now it needs to be relearnt).

Properties of a card.

  • due: the timestamp for when the card should be shown again.
  • left: how many learning steps before the card graduates to review.
  • reps: how many times the card has been shown.
  • ivl: tracking the card interval (time to add when recalculating due).

Input

When answering the flash cards there's four inputs you can provide, each of which will cause a different effect, these decide the cards position in one of two or three queues. The result of these inputs also depends on which queue the card is currently in. You can see this here

  • Again: Reset number of training reps
  • Hard: +1 rep, show again in 1 min
  • Good: +1 rep, show again in 5 min
  • Easy: +1 rep, show again in 1 day

Development

Up until this point I hadn't done a single line of code. The research done above was undertaken before I started writing any code. This helps me have a clear understanding of what I'm actually trying to do. It's far easier to scribble away in my notebook that it is to change code.

There are a few things I needed, which I learnt after I'd written the tool. Things such as being able to edit a card, but these were not major features.

The other thing that I find interesting (or obvious) is solution for the queue. During a learning session you need to have the card queues be dynamic since you're popping the earliest due card and then you're sorting them by when they are next due so the implementation fits using a heap quite well. You can see me initialising the heap here.

Conclusion

I really enjoyed reading up on some of the complexities involved in getting the spaced repetition working in a way that allows new topics to be slowly introduced while working on cementing topics that were in the progress of being learnt. It feels a little exciting when you encounter something new during the learning session. I used kanki to prepare on some topics and every so often add new cards to system design.

One thing I found quite useful is getting someone else to ask you the questions verbally and they make a judgement on how close you are to the answer. This can get you more comfortable with talking about these topics and the person reading the questions can help keep you honest about how well you know a topic.

Lastly it's also interesting that learning is effectively broken down into three separate speeds. 1. This is new. 2. I'm actively learning this. 3. I should know this.

S Williams-Wynn at 12:38| Comments()

Mon 24 January 2022

Configuring Traefik with Redis

Over a weekend I had a quick play around with the config provider to traefik, experimenting with reverse proxies. I'm fairly familiar with the redis-cli and it's fairly easy to build a redis client in any language so getting off the grounds is a 10min job at most.

I've always got redis container running locally, so I'm sticking with that, to get this running I do:

docker run --name some-redis -p 6379:6379 -d redis

Since I am running traefik on docker-compose and I'll need it to connect to redis, I have to create a network and redis should be connected to this network:

docker network create mynet
docker network connect mynet some-redis

The next step is the compose file for traefik:

# docker-compose.yml
version: '3'

networks:
  maunet:
    external: true

services:

  reverse-proxy:
    image: traefik:v2.5
    command:
      - "--api.insecure=true"
      - "--providers.redis"
      - "--providers.redis.endpoints=some-redis:6379"
    ports:
      # HTTP
      - "80:80"
      # Web UI
      - "8080:8080"
    networks:
      - maunet

And up:

docker-compose up -d

The traefik frontend should be accessible at 127.0.0.1:8080.

Configuration

A few of the traefik concepts didn't make sense at first, but it didn't take long to understand what they're talking about.

  • I have another container running a simple rest server, it's serving on port 3000 which is exposed at port 53782 this container is called 'some-server'.

  • A hurdle that took me a while to figure out was to also connect this container to the network we have traefik and redis running on:

docker network connect mynet some-server
  • I need to tell traefik a service (server) exists:
set traefik/http/services/<service-name>/loadbalancer/servers/0/url "http://some-server:3000"
  • Now assign this service to a router:
set traefik/http/routers/newroute/service some-server
  • Lastly give the router a rule: (Note those are backticks around the forward slash.)
set traefik/http/routers/newroute/rule "Path(`/`)"

Now hitting 127.0.0.1:80 will forward the request to your server. You can also do all the other interesting things that traefik provides like loadbalancing and stripping path prefixes.

References

  • https://doc.traefik.io/traefik/routing/providers/kv/
S Williams-Wynn at 00:27| Comments()

Mon 31 August 2020

Python Deque

This is now my third article on lists. As someone that uses the built-in python list on a fairly regular basis, I might have built up a false sense of security. I'm pretty familiar with these listy-boys. However, recently I found out that I was not thinking about them correctly. Readers might smack themselves if they're familiar with data-structures but don't know how lists are implemented internally. The built-in lists are dynamic arrays.

How else could they optimise a sweet O(1) lookup time on indexing: mylist[4]. Especially when analysts are trying to avoid the built-in iterator and cursing their code with: for i in len(mylist): mylist[i].

Another trait an established data-structurer with be familiar with when it comes to dynamic arrays is that the append and pop methods are an amortised O(1). Amortised; because occasionally you have to suffer a cost of realloc(ating) memory across larger arrays.

Where the list starts to suffer is from poping and inserting at arbitrary positions.

Linked-List

The data-structurer will have had the linked-list slammed into their head often enough that it will pain them to hear about it again. So theory aside, I'll give you that sweet O(1) append and last item pop that you expect from a performant Stack.

Python deque provides a comparatively larger performance hit on initialisation to list and has poor O(n) performance when you want any arbitrary item somewhere in the middle. It does, however, have O(1); popleft, pop, append and appendleft. Due to being a doubly-linked list (or double-ended queue to get the abbreviation deque)

Deque in the wild

I saw a nice little quote from an enginneer on Quora:

In 8 years of getting paid to write computer programs, this post is the only time I’ve typed ‘deque.’

There are many places deque is used in the stdlib, most commonly whenever someone needs a queue or stack such as constructing a traceback, parsing python's sytax tree and keeping track of context scope.

My little run-in with deque was using it instead of a recursive function to avoid python's

maximum recursion depth exceeded

This limit happens to be set to 10^4. The solution was to add child nodes to a deque and when you were done with analysing the current node, popleft the next node.

Python Queue

You might be tempted to ask, well if deque is for queues. What on earth is from queue import Queue.

These queues are different (although, still using deque under the hood). They are optimised for communication across threads, which need to involve locking mechanisms and support methods like put_nowait() and join(). These are not intended to be used as a collective data-structure, hence the lack of support for the in operator.

More information

There is some neat documentation in the cpython repo which contains more data-structures and other alternatives to the standard built-in list. Tools for working with lists

References

S Williams-Wynn at 14:30| Comments()

Thu 16 April 2020

Module not found Heroku

There are some bugs and problems that give a thrill once they're solved. The best bugs are the ones that teach you something, the worst bugs are the ones that indicate that you've not improved your spelling and the difference between an l and a 1 is large.

Figuring out why I was facing the following traceback in heroku provided a time consuming learning experience, but probably one that I won't forget.

heroku[web.1]: State changed from crashed to starting
heroku[web.1]: State changed from starting to crashed
heroku[web.1]: State changed from crashed to starting
app[web.1]: Traceback (most recent call last):
app[web.1]: File "/home/app/server/bin/server", line 3, in <module>
app[web.1]: from api.server import deploy
app[web.1]: ModuleNotFoundError: No module named 'api'

Why can't the module be found 🤔!

Worst of all, the dockerfile builds locally, and when I run it. It's executing /home/app/server/bin/server and ready to receive traffic...

FROM python:3.8.2

ENV USER appuser
ENV HOME /home/${USER}

RUN mkdir -p ${HOME}/server
WORKDIR ${HOME}/server

ENV PATH ${HOME}/server/bin:${HOME}/.local/bin:$PATH

USER appuser

CMD ["server"]

I won't go into how long I spent on thinking it had something to do with permission. Looking back, it's quite clear that a permission has nothing to do with it, since the logs would say so.

Take a step back

The difference between the image on heroku and the image running locally. What probably made me assume it was a permission error was the quote from their docs:

containers are not run with root privileges in Heroku

So there was some funky business they're doing to the user I provided.

Get closer to the problem

Finding out that I could get inside the heroku container certainly helped me figure out the problem:

heroku run bash

I could now recreate the Module not found error. I tried using pipenv to install the module that was missing, however that didn't work either. hmm.. Where are these modules installed??

Show me the site-packages:

python -m site

Heroku

sys.path = [
    '/',
    '/usr/local/lib/python38.zip',
    '/usr/local/lib/python3.8',
    '/usr/local/lib/python3.8/lib-dynload',
    '/home/appuser/server/.local/lib/python3.8/site-packages',
    '/home/appuser/server',
    '/usr/local/lib/python3.8/site-packages',
]
USER_BASE: '/home/appuser/server/.local' (exists)
USER_SITE: '/home/appuser/server/.local/lib/python3.8/site-packages' (exists)
ENABLE_USER_SITE: True

Local Docker Container

sys.path = [
    '/home/appuser/server',
    '/usr/local/lib/python38.zip',
    '/usr/local/lib/python3.8',
    '/usr/local/lib/python3.8/lib-dynload',
    '/home/hints/.local/lib/python3.8/site-packages',
    '/usr/local/lib/python3.8/site-packages',
]
USER_BASE: '/home/appuser/.local' (exists)
USER_SITE: '/home/appuser/.local/lib/python3.8/site-packages' (exists)
ENABLE_USER_SITE: True

Right so they are not referencing the same site-packages. Are the site packages even installed in the heroku container?!?

$ ls /home/appuser/.local/lib/python3.8/site-packages
Flask-1.1.2.dist-info               mccabe-0.6.1.dist-info
Flask_Alembic-2.0.1.dist-info       mccabe.py
Flask_Cors-3.0.8.dist-info          more_itertools
Flask_JWT_Extended-3.24.1.dist-info more_itertools-8.2.0.dist-info
Flask_SQLAlchemy-2.4.1.dist-info    oauthlib

🎉 so they aren't missing...

Locally

echo $HOME
/home/hints

Heroku

$ echo $HOME
/home/hints/server

Ah.. So $HOME has something to do with it.

Well I have $HOME=/home/appuser and WORKDIR=/home/appuser/server perhaps heroku is setting the work directory to the home directory. 🤷‍♂️

ENV USER hints
ENV HOME /home/${USER}

RUN mkdir -p ${HOME}
WORKDIR ${HOME}

And sure enough fixing my deployment.

S Williams-Wynn at 00:03| Comments()

Thu 06 December 2018

Chain of Responsibility

Easily one of my favourite patterns from the gang of four is the chain of responsibility pattern. It aims to avoid coupling the object that sends a request to the handlers of the request. This is especially useful if the structure to our handlers follows a sense of hierarchy.

I've had very few opportunities to implement design patterns, but gaming has really helped me to envision a context where several designs can be applied as handy solutions to managing complexity.

So we will apply the chain of responsibility to a situation where we have gathered four of the most powerful wizards in a room, each of whom will test their power by casting a spell.

For this example we have our wizards as the objects which send requests.

class Wizard:
    """Create a wizard."""
    def __init__(self, name: str, intelligence: int):
        #: Identify the wizard by name
        self.name = name
        #: Intelligence as a proxy of a wizard's power.
        self.intelligence = intelligence

    def cast_spell(self, spell: Spell):
        """Have the wizard cast a spell."""
        print(f"{self.name} casts {spell.name} spell.")
        spell.cast(self)

We will make each behaviour of the spell, which is determined by the power of the wizard casting it, as an object handling a request. If the wizard does not meet the handler's requirements it passes the request on, to it's successor. This is where we have applied a sense of hierarchy to the handler.

To begin we have an Abstract Handler:

from abc import ABC
from abc import abstractmethod


class AbstractHandler(ABC):
    def __init__(self, successor=None):
        self._successor = successor

    def handle(self, creature):
        reaction = self._handle(creature)
        if not reaction:
            self._successor.handle(create)

    @abstractmethod
    def _handle(self, spell):
        raise NotImplementedError("Must provide implementation in subclass.")

Now we can define our handler hierarchy:

class LowPowerFireSpell(AbstractHandler):
    def _handle(self, creature):
        if creature.intelligence < 10:
            print("Spell backfires.")
            return True


class MediumPowerFireSpell(AbstractHandler):
    def _handle(self, creature):
        if creature.intelligence < 20:
            print("Small fire ball is cast.")
            return True


class HighPowerFireSpell(AbstractHandler):
    def _handle(self, creature):
        if creature.intelligence < 30:
            print("A fire ball blazes across the room")
            return True


class GodlikePowerFireSpell(AbstractHandler):
    def _handle(self, creature):
        print("A Massive column of fire burns through the room!")
        return True

Finally a single object to identify the spell chain.

class Spell:
    def __init__(self, name: str):
        #: Identifying the spell by a name
        self.name = name
        #: How the spell behaves at differing levels of user's power
        self.chain = LowPowerFireSpell(
            MediumPowerFireSpell(
                HighPowerFireSpell(
                    GodlikePowerFireSpell()
                )
            )
        )

    def cast(self, creature):
        self.chain.handle(creature)

To test their skill, we have a spell which casts a "Fire Ball". We have the following wizards present:

if __name__ == '__main__':
    fire_spell = Spell('Fire Ball')
    merlin = Wizard("Merlin", 8)
    albus = Wizard("Albus", 18)
    howl = Wizard("Howl", 28)
    gandalf = Wizard("Gandalf", 38)

They are all gathered in a room, and take turns casting the same spell.

room = [merlin, albus, howl, gandalf]
for wizard in room:
    wizard.cast_spell(fire_spell)
    print("")

To which we should see the spell behave according to their intelligence:

(myenv) pc-name ~ $ python script.py
Merlin casts Fire Ball spell.
Spell backfires.

Albus casts Fire Ball spell.
Small fire ball is cast.

Howl casts Fire Ball spell.
A fire ball blazes across the room

Gandalf casts Fire Ball spell.
A Massive column of fire burns through the room!

(myenv) pc-name ~ $

Implementation can be found here

S Williams-Wynn at 00:40| Comments()

Thu 11 October 2018

Iterator Design Pattern

Before I started reading the gang of 4 ["Go4"], I was convinced I would not need an iterator. After all, in python, they are already implemented:

x = ['a', 'b', 'c']

for i in x:
    print(i)

In the frame of python lists as aggregate objects, the intent of an iterator is satisfied.

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representations.

Displaying notes

Here I explain, how the iterator became my favourite design pattern.

I have written about representation of lists before, and I don't think this will be my last time. When implementing the drop-downs in foolscap to allow a user to see sections contained in their note, I ran into an issue of complexity where I had blocks of conditionals dictating what should be displayed to a user. I wanted the user to have an indication that a note contains sections, and for them to be able to toggle a drop-down to see each section. Similar to:

 (+) | circleci  |
     | docker    |

Where my "circleci" note would contain sections and the "docker" note does not. Then expanding:

 <-> | circleci  |
  └─ | -workflow |
     | docker    |

This is where I realised the abstraction power of an iterator, And I could hide the collapsed sections behind an "if" conditional in an iterator.

class Menu:

    def __init__(self, items):
        self.items = items

    def visible(self):
        """Yield the next appropriate item for display."""
        for item in self.items:
            yield item.title
            if hasattr(item, 'expand') and item.expand:
                for sub_item in item.sub_items:
                    yield sub_item.title

Supporting further traversal policies in my Menu class is straightforward, and drawing becomes absurdly simplified:

def draw(menu):
    """Draw all viewable menu items."""
    for item in menu.visible():
        draw_item(item)

After this I thought of a more complex aggregate structure that I could traverse, like a tree depth first.

Realising the abstraction strength of the iterator, one finds its definition of intent far more compelling.

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representations.

S Williams-Wynn at 18:37| Comments()

Wed 10 October 2018

Random VM Name

Something I found to be cute about the docker engine is it's feature to create a random name for your containers, when one is not specified.

I've had some good ones e.g:

objective_galileo
distracted_euclid

Occasionally I'd be spinning up the odd VM and have to provide some generic name like dev-sebastien. So instead I thought my own arbitrary name generation could save some time here.

Random names

The idea was to combine <adjective>-<pokemon> and use this as the VM parameter.

Thus defining the following in .create_name.sh

function gen_name()
{
    pokemon=(
        "pidgey"
        "pikachu"
        "cubone"
    )
    adjectives=(
        "hungry"
        "adorable"
        "jittery"
    )

    select_pokemon=${pokemon[$RANDOM % {#pokemon[@]}] }
    select_desc=${adjectives[$RANDOM % {#adjectives[@]}] }

    local result=sebastien-$select_desc-$select_pokemon
    echo "$result"
}

Call this in your .bash_aliases file.

source .create_name.sh

And every time you run gen_name in your command line, you'll get a new "vm" name.

I've coupled this with:

create_instance(){
    gcloud compute instances create $(gen_name);
}

Here are some of the names of my machines:

sebastien-livid-quagsire
sebastien-lonely-metapod
sebastien-gentle-rattata
S Williams-Wynn at 19:19| Comments()

Tue 09 October 2018

Foolscap List Ordering

Foolscap was my answer to losing all my notes and forgetting the exact file I saved it.

Was it on Desktop or next to the project?

Thus I decided to write an application that took care of the saving and finding of my notes.

Unordered Lists

One thing I have taken for granted is the ordering of my files in any file system. I only realised how important it was after unordered dictionaries in python allocated my notes random positions on the list.

The random ordering meant if I have note "M" in mind, it could appear anywhere on the list. So my eyes would track down n positions in the list to find "M". An O(n) operation for my eyes.

After growing tired (a whole day of O(n) eye operations), I wrote the rule to provide me the list in alphabetical order. This was great my brain automatically switched to doing a binary search for note "M". O(log n)

Ordered Lists

Now that finding note "M" was optimised for my eyes, my finger had to press the down key until my cursor was on the note of choice. This wasn't good enough, I was referring to note "M" several times a day and the other notes I'd reference once a month were adding computation time to my finger.

This is where I introduced two of the most powerful rules of Foolscap. Since I tracked the number of views, and time it was last viewed or modified I could implement the following ordering:

1: LastView/Modified/RecentlyCreated
2: MostViewed
3: 2ndMostViewed
4: 3rdMostViewed
5: A
6: Z

This was neat!

I had significantly cut down the amount of time it took for me to get to the notes I was using most frequently.

Search Lists

Searching needed a slightly different ordering since applying the rules above to your search results will likely be meaningless. The meaningless will come from your inability to predict what will appear in the list to begin with.

Python has a neat method for it's strings.

note_title.startswith("text")

Which I used as the initial search feature. A search by the prefix of my note title. To me this made sense since my notes are titled by their subject matter, e.g. "linux", "elastic".

The iteration to this search functionality was to include the conditional:

if "text" in note_title:

I still wanted to provide the user with the result of the initial prefix search as the first result in the list.

This is when I fathomed this solution:

search = [title for title, _ in notes.items()
          if query in title]

# Sort by the position the query appears in the title
return sorted(search, key=lambda title: title.index(query))

Pretty neat!

S Williams-Wynn at 22:37| Comments()

Tue 09 October 2018

Procedural Villages

My inspiration for any tool or project has always been the thought that it could be done better, either that or I'd feel like I would benefit a lot from a missing feature.

In my first embarked I attempted to create a dungeon crawler, full of all the creatures and characters I thought were missing from traditional games.

It was through brute-force of the Roguebasin tutorial where I learnt how to code.

I must have created the same game 4 or 5 times before I decided to scrap the libtcod library and create a game without an interface.

Armed with stdout and turn-based printing, I implemented feature after feature. One of the features I was quite proud of was the randomly generated villages I'd spawn my player into.

Objects

I was aiming for something simple, just populate the map with houses that the player can interact with, for this I deconstructed a house into a rectangle.

class Rect:
    def __init__(self, x, y, h, v):
        self.x1 = x
        self.y1 = y
        self.x2 = x + h
        self.y2 = y + v

    def center(self):
        center_x = (self.x1 + self.x2) / 2
        center_y = (self.y1 + self.y2) / 2
        return (center_x, center_y)

    def internal(self, x, y):
        """
        [ ][ ][ ][ ]
        [ ][X][X][ ]
        [ ][X][X][ ]
        [ ][X][X][ ]
        [ ][ ][ ][ ]
        """
        ...
        return bool()

    def edges(self, x, y):
        """
        [X][X][X][X]
        [X][ ][ ][X]
        [X][ ][ ][X]
        [X][ ][ ][X]
        [X][X][X][X]
        """
        ...
        return bool()

    def sides(self, x, y):
        """
        [ ][X][X][ ]
        [X][ ][ ][X]
        [X][ ][ ][X]
        [X][ ][ ][X]
        [ ][X][X][ ]
        """
        ...
        return bool()

The poorly defined object above could now provide boolean confirmation to a co-ordinates existence in appropriate sections of a rectangle.

From the above Rect class I can place a door on a Rect.sides() and I can fill the area defined by Rect.internal() with items.

Empty space: | . |
Wall:        | # |
Monster:     | m |
Door:        | + |

| . | . | . | . | . | . |
| . | # | # | + | # | . |
| . | # | m | . | # | . |
| . | # | . | . | # | . |
| . | # | . | . | # | . |
| . | # | # | # | # | . |
| . | . | . | . | . | . |

Throwing in some size variations and randomising the house position on the map, (making sure there are no houses intersecting each other). Allowed me to generate maps that looked like these:

Example 1

| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | # | # | # | # | # | # | . | # | # | # | # | . | . | . |
| . | # | u | . | . | @ | # | . | # | . | m | # | . | . | . |
| . | + | . | . | . | . | # | . | # | . | . | + | . | . | . |
| . | # | # | # | # | # | # | . | # | # | # | # | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | > | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | # | # | + | # | # | # | . | . | . | . | . | . | . | . |
| . | # | . | . | r | . | # | . | . | . | . | . | . | . | . |
| . | # | . | . | . | . | # | . | . | . | . | . | . | . | . |
| . | # | # | # | # | # | # | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |

Example 2

| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | # | # | + | # | . | . | . | . |
| . | . | . | . | . | . | . | # | . | . | # | . | . | . | . |
| . | . | . | . | . | . | . | # | . | . | # | . | . | . | . |
| . | . | . | . | . | . | . | # | m | . | # | . | . | . | . |
| . | . | . | . | . | . | . | # | # | # | # | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
| . | . | . | . | . | . | . | . | . | . | . | # | # | # | # |
| . | # | # | # | # | # | # | # | . | . | . | # | . | . | # |
| . | # | . | . | . | . | u | # | . | . | . | + | . | . | # |
| . | + | . | . | . | . | . | # | . | . | . | # | > | . | # |
| . | # | X | . | . | . | . | # | . | . | . | # | . | r | # |
| . | # | # | # | # | # | # | # | . | . | . | # | # | # | # |
| . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |

Unfortunately my code was not beautiful back then. I still think it was a neat idea

S Williams-Wynn at 21:48| Comments()