whydoitweet

I'm a Lecturer in Computer Science at Brunel University London. I teach games and research simulations. Views are my own :).

Today, in the spirit of xrpguitar, Steve Lukathar and many other guitar-enabled artists we are going to model a vibrating (guitar) string.

Sometimes I have to make simulations entirely from scratch, but this time I found an existing tutorial that actually helped me part of the way. In that post the author focuses mainly on the physics and not so much on the code, and the implementation with matplotlib is rather slow.

For this tutorial, I made a modified implementation that is (a) shorter, (b) in PyGame (so we can extend it later) and © a bit easier to adjust. I also plan to focus more on explaining the code, and a bit less on the pure physics.

The Physics part

Since the existing tutorial covers the physics side rather well, I'll keep the discussion brief here. Essentially we assume that the string is fixed on both ends (like with a guitar). To then make a model of it we use something called the wave equation, which combines a wave travelling to the left, and one to the right:

Here, x is the horizontal position and u(x,t) is the vertical position of a piece of guitar string in the simulation. Then there is a generic function for a wave travelling to the left (f(x-ct)) and for a wave travelling to the right (g(x+ct)).

In this tutorial, we use a specific version just to get something visible and cool. We will make both the left-travelling and right-travelling parts similar in magnitude, so that we have a standing wave:

Now all we have to do is to figure out what all these letters mean and should be, and resize the whole thing to fit our 800x600 screen, and then we're practically done! :)

Setting up the simulation

Unsurprisingly perhaps, we're going to use PyGame again. Why?

  • Because it performs well.

  • Because it's much easier to make more extended interactive simulations off a PyGame code than off a static pyplot.

  • And because it actually seems to lead to a more compact and elegant code (!)

Please see this tutorial to see how you can create a PyGame Repl, which is exactly what we need to do here now :).

The whole thing is a mere 31 lines of code (nice and compact, right?), and the first 10 lines you already get for free, as we need those to initialize PyGame (they've been the same in many of my tutorials):

import pygame

import time

import numpy as np

from numpy import pi

pygame.init()

width, height = 800, 600

backgroundColor = 0, 0, 0

screen = pygame.display.set_mode((width, height))

screen.fill(backgroundColor)

Okay, not totally the same, because we are importing numpy this time for calculating the sine values. In an ideal world we all know/recall exactly what the sine is, and no one will actually click that Wikipedia link, but then again we don't live in an ideal world and I for sure forgot a lot of my high school knowledge. We also

import the notion of pi (3.1415 etc...).

Slamming in the equation

Before we put in the equation itself, we need to define what the constant values in that equation (and in the simulation) should be, these are:

c = 1 # wave speed

dt = 0.1 # time increment

string_length = 4 # string length

You can tinker with the first two to get faster or slower moving strings, and with the third one to change the length (period) of your waves.

The wave equation example itself is a function u(x, t), and we can translate it directly into the Python code like this:

#Wave equation solution

def u(x,t):

return 0.5*(np.sin(x+c*t) + np.sin(x-c*t))

As you can see, it looks simple once you know and understand the equation it is derived from. However, if you don't understand the equation (e.g. you come across the code without having seen the equation), these mathematical notations can actually look a bit confusing...

The main loop

We're already half way now! So, before the main loop we need to initialize the timer,

t = 0

upon which we can start the main loop itself, which I made infinite.

while True:

We fill the screen with the black background color, to remove any previous string visualizations,

screen.fill(backgroundColor)

and then will calculate and render each of the 800 pixels of the string, one by one:

for sx in range(0, 800):

In this sub-loop we first have to rescale the screen x value (sx) from a scale of the screen length to a scale of a single string wave segment in our wave equation. We do this by multiplying it by the string_length*pi, and dividing it by the width of the screen in pixels:

x = ((float(sx) / 800.0) * float(string_length)*pi)

Next, we plot our pixel. The x coordinate is our screen x value (sx), but the y coordinate is a bit more intricate. It is initially the result of the converted x value (x) applied to our wave function (u(x, t)). After that we multiple the value by 600/pi to convert the scale back from the equation scale (height of pi) to the screen scale (which has a height of 600 pixels). We also add 300 pixels, so that any resting string (y=0) will be in the center of the screen. Lastly, we color the pixel white ((255, 255, 255)). Now that's a lot of stuff, but it all results in only one line of code that sets one pixel!

screen.set_at((sx, int(u(x, t)*600.0/pi + 300)), (255, 255, 255))

After we've done that for all 800 pixels, we simply increment our timer by dt (the time step size which we defined earlier on).

t += dt

Display the whole thing on the screen and sleep for 20 milliseconds!

pygame.display.flip()

time.sleep(20 / 1000)

Once you have all the code in, you should get an animation like this, but more smooth:

I just hand-stacked 4 frames together here, so apologies if it looks choppy ;).

Closing thoughts

This is certainly one of the easier tutorials I've created, but a vibrating string is such a quintessential simulation scenario, that I felt I just couldn't run a blog without it! Next time I'll try to do something quite different again!

Now all that's left is me sharing the source code and Repl. Also, for subscribers I'll signpost to a few more nice blog entries on other topics from the student (Mic) who made that initial tutorials, as they are quite hard to track down on his page. I might make an improved version of a few of those tutorials in due time, but definitely not all of them (some are far too niche for my liking).

(Back to Small Sims Index)

Credit

Header image courtesy of pxhere.com (CC0).

Appendix A: Source code and Repl

import pygame

import time

import numpy as np

from numpy import pi

pygame.init()

width, height = 800, 600

backgroundColor = 0, 0, 0

screen = pygame.display.set_mode((width, height))

screen.fill(backgroundColor)

# Original Source (with matplotlib): http://firsttimeprogrammer.blogspot.com/2015/07/the-wave-equation-2d-and-3d.html

c = 1 # wave speed

dt = 0.1 # time increment

string_length = 4 # string length

#Wave equation solution

def u(x,t):

return 0.5*(np.sin(x+c*t) + np.sin(x-c*t))

t = 0

while True:

screen.fill(backgroundColor)

for sx in range(0, 800):

x = ((float(sx) / 800.0) * float(string_length)*pi)

screen.set_at((sx, int(u(x, t)*600.0/pi + 300)), (255, 255, 255))

t += dt

pygame.display.flip()

time.sleep(20 / 1000)

And the Repl is available here.

Read more...

I am sure I am not the only one who has presented his work, only to receive the all too common question which adheres to the following template:

Aren't you just ?

Now very often these types of criticism are about as well-thought through as the original saying, which means: not very much ;).

In this post I'll collect a few simple responses against these common types of criticisms. And for amusements sake, I'll give you estimates of how often I have received such comments!

Okay, let's get started:

1.

Aren't you just reinventing the wheel?

Tally: approximately 50-100 times in my life.

This one is often phrased when I am developing a particular technique, or software tool, or computer game algorithm, or writing a story...or basically anytime I choose to go creative and rework something from scratch rather than pick up something existing.

It is an easy one to pick apart, because the wheel has been reinvented many times, and with great success. For instance, wheel reinventions led to:

  • The emergence of tyres, that are now present on almost any wheel.
  • The emergence of continuous tracks, e.g. for tanks.
  • And most recently, the emergence of the shark wheel, which is not even circular anymore, yet performs better in the presence of debris and water.

Just as an example, back in WW1 they did try to make tanks without reinventing the wheel. It led to something like this:

Credit: *“L'Aube de la Gloire” Alain Gougaud, p.107*.

If you wonder why this didn't work out, just imagine the rear wheel hitting a rock, or a swampy bit of terrain...

Okay, so with that it should be established that wheel reinvention is often actually a good thing.

The secondary point that may arise in further response is whether your use of the reinvented wheel is indeed different from the uses that all the original wheels were intended for. And that is already a much better question to ask and answer :).

2.

Aren't you just doing engineering?

Tally: about 10 to 20 times in my career.

In the almighty field of science and academia, the notion of “engineering” can sometimes be used in a condescending manner, implying that whatever it is that you are doing simply has no scientific merit whatsoever. This is usually, if not always, nonsense. You can put a scientific perspective on almost anything you “engineer”, and in fact conducting science without engineering probably would herald the death of fields such as computational astrophysics, fluid dynamics modelling, or in general the more applied half of the Science Technology, Engineering and Maths disciplines in any case.

But hey, people relate easily to examples, right? So let's bring on the examples:

  • Would Alexander Fleming have discovered penicillin if he didn't go through the “engineering” motions of creating and maintaining a wet lab? Given his back story, I strongly doubt it.
  • Or Erik Holmberg relentlessly choreographing the manual placement and movement of light bulbs, based on readings from light-sensitive paper, only to be later heralded as the first N-body simulation builder?
  • And what about this airplane thing:

Wright Flyer. Source: wikimedia.

To put a cynical take on this: “real” scientists would of course conjure their airplanes out of thin air with sheer inductive reasoning, rather than stoop as low as to resort to engineering...

Anyway, whether we call something science and/or engineering shouldn't be the point here. What really matters is whether we're trying to achieve something that hasn't been achieved before.

3.

Aren't you just putting old wine in new bottles?

Tally: only once. But it was by a senior academic in a job interview talk for lecturer.

As a computer scientist, this one really is my personal favorite. I received it after having given a 20-minute research talk and, quite uncommon for me frankly, that particular time I managed to articulate the right response the first time around:

“Isn't computer science all about the bottles, and not the wine?”

And on that note I do think it's appropriate to finish this blog post, and given the weekend provide an opportunity to appreciate that the wine matters, as well as the bottles of course :).

Credit

Header image courtesy of Thomas Guest: https://www.flickr.com/photos/thomasguest/5491482766.

Read more...

Every game world needs towns, with people to talk to. And today, we'll make a start to construct such a town. The goal will be:

  • To port our previous world walkabout map to a town setting.
  • To add NPCs and their sprites in an easy way to the town map.
  • To make a mechanism that allows the player to have a short chat with the NPCs.

It took some effort, but all in all I still managed to get this to work on repl.it, using a total of 105 lines. This is the first of a series of posts on this topic, as I will be adding more and more elements to the town setting (hopefully without extending the code too much!).

Step 1: gather and upload source material

First, you're going to need some source material, namely the town map and sprite image for the people walking around. You can (and eventually should) make your own, but for your convenience I've attached the ones I've used.

This is the map I've used (an image taken from my own game project):

And here are the three sprite files that I chose to use:

Now for some reason the third image is shown larger in my blog post (Coil bug?), but rest assured that all images are 100x200 pixels, with each frame being 48x48 pixels + a 2 pixel barrier on the right and at the bottom of each frame.

Okay, now onward to the coding!

Step 2: Setting up the town scene

Once more, we need to create a PyGame Repl (see here for how to do that), and initialize PyGame at the start. We do that as follows:

import pygame

import time

import sys

pygame.init()

width, height = 800, 600

backgroundColor = 0, 0, 0

screen = pygame.display.set_mode((width, height))

screen.fill(backgroundColor)

Next up, we need to define a class for the different people in our town. I chose to call the class NPC (non-player character) as it could include humans, but also animals or monsters. I give each NPC 5 fields, namely an x,y coordinates for their location, a txt field containing the phrase they will say, and two image variables: one pointing to the sprite image file, and one containing the image once it's actually loaded.

In code, that will then look as follows:

class NPC:

def __init__(self, x, y, txt, img):

self.x = x

self.y = y

self.txt = txt

self.img = img

self.sprite = pygame.image.load(img)

Next up, we need to load the town map, the player sprite, and store the dimensions of the town map. We do this using the exact same code we used in Game World 4 :):

map_img = pygame.image.load('map_full.png')

pl_img = pygame.image.load('seolean.gif')

map_width, map_height = map_img.get_rect().size

But this time, we add three characters to it! We do that using the following one-liner:

npcs = [NPC(400, 600, "Hello!", "seolean.gif"), NPC(900, 600, "Stay on your guard.", "guard.png"), NPC(500, 1100, "I am doing archery, watch out for my arrows!", "archer.png")]

This adds a clone of the player (saying “Hello!”), a guard character and an archer character to the map.

We also need to set a range of default variables:

x = 0

y = 0

px = 0

py = 0

f = 2 # 0 up, 1 right, 2 down, 3 left

show_text = False

npc_text = ""

pygame.font.init()

myfont = pygame.font.SysFont('Comic Sans MS', 30)

x, y, px and py all come from the World Map tutorial, while f indicates the direction that the player is facing. I also added a variable toggling whether NPC text is shown (show_text), as well as one storing the text of the NPC that the player currently interacts with (npc_text).

Lastly, we initialize and load a font, as we will need that to display text on the screen.

Step 3: set up collisions

Now a big difference with the previous tutorial is that we now have multiple characters, i.e. our player may bump into other characters.

To prevent this from happening, we need to set a bounding box for each of the characters. In our case, these bounding boxes need to be about 48 pixels wide, and be centered on the sprite's x location. However, they need to 96 pixels high, because we don't sprites to be rendered on top of each other.

We then use this bounding box to detect whether a player sprite, residing at a location (px,py), intersects with one of the NPC sprites. We do that as follows:

def detect_collision(px, py):

for k,i in enumerate(npcs):

if i.x - 24 < px < i.x + 24 and i.y - 48 < py < i.y + 48:

return k

return -1

The function returns a value k if such an intersection is present, with k being the ID of the NPC it intersects with (i is the actual object).

Now to fully implement this we will need to call this during movement, which brings us to...

Step 4: the main loop

Be prepared: because we have a game with animation, player movement, collisions and conversations, this main loop is going to be pretty large. I could have trimmed the loop and spin off parts in other functions, but since this is a tutorial I prefer to be as transparent as possible. So here we go:

while True:

1. We need to identify which keys have been pressed:

for event in pygame.event.get():

if event.type == pygame.QUIT:

break

keys = pygame.key.get_pressed()

2. We initialize a variable detecting whether a collision has occured during this iteration:

coll = -1

3. Next up is the player movement. We reuse the code from the previous tutorial, and add two lines at the start of each keypress branch:

if keys[pygame.K_LEFT]:

3.a We first check whether the intended new player location will result in a collision with other sprites:

coll = detect_collision(px-5, py)

If not, we use our original code:

if coll == -1:

px -= 5

px = max(0,px)

f = 3

...and also make sure that no NPC text is shown anymore, because the player is now moving around again.

show_text = False

4. We repeat this exact pattern for the other three keys:

if keys[pygame.K_RIGHT]:

coll = detect_collision(px+5, py)

if coll == -1:

px += 5

px = min(px,map_width)

f = 1

show_text = False

if keys[pygame.K_UP]:

coll = detect_collision(px, py-5)

if coll == -1:

py -= 5

py = max(0,py)

f = 0

show_text = False

if keys[pygame.K_DOWN]:

coll = detect_collision(px, py+5)

if coll == -1:

py += 5

py = min(py,map_height)

f = 2

show_text = False

5. If a player does collide with another sprite, we will assume that it chats to the NPC. Therefore, we will show the corresponding text of the NPC the player interacts with (the ID of the NPC is stored in coll):

if coll >= 0:

show_text = True

npc_text = (npcs[coll].txt)

6. We show the map and the player as we did in the last tutorial:

x = max(0, min(px - int(width/2), map_width-width))

y = max(0, min(py - int(height/2), map_height-height))

screen.blit(map_img, (0, 0), area=(x, y, width, height))

screen.blit(pl_img, (px-x, py-y), area=(0, f*50, 48, 48))

7. But we also show our NPCs this time!

for i in npcs:

screen.blit(i.sprite, (i.x-x, i.y-y), area=(0, 100, 48, 48))

8. I comment out the print statement from the last tutorial, to speed up the game a little...

#print("px: {}, py: {}, f: {}".format(px,py,f))

9. But I do render a simple text box whenever show_text has been set to True (i.e., when the player collided with an NPC):

if show_text:

pygame.draw.rect(screen, [136,136,136], pygame.Rect(100,520,600,75))

text_surface = myfont.render(npc_text, False, (0, 0, 0))

screen.blit(text_surface, (105,525))

10. And lastly, I display everything, and sleep for 20ms, giving a maximum frame rate of 50 fps.

pygame.display.flip()

time.sleep(20 / 1000)

Once you put all this together and run it, you'll get a starting screen like this:

To operate the game, you will have to click on the screen, so that your keyboard is focused on it. You can then use the arrow keys to move around. You'll find that you can move through walls and trees (next time...) but not through NPCs, thanks to our collision engine.

When you bump into an NPC, something like this will happen:

Ending the “conversation” is easy: you just simply move elsewhere!

Closing thoughts

This tutorial again took a long time to put together, but I do think we really made a good step towards a working computer game here! Feel free to toy around with the code, and at some point I'll make a post about how we can refactor it, so that we can more easily extend it further down the line.

And if anything is unclear or confusing, please let me know straight away (Twitter:@whydoitweet) as I'll happily bugfix this tutorial!

Lastly, I'll attach the full source code and Repl, and for subscribers I'll give a few suggestions how you can quite easily extend this code.

Appendix A: Full source code and Repl

The source code is here:

# Code accompanying this tutorial:

import pygame

import time

import sys

pygame.init()

width, height = 800, 600

backgroundColor = 0, 0, 0

screen = pygame.display.set_mode((width, height))

screen.fill(backgroundColor)

class NPC:

def __init__(self, x, y, txt, img):

self.x = x

self.y = y

self.txt = txt

self.img = img

self.sprite = pygame.image.load(img)

map_img = pygame.image.load('map_full.png')

pl_img = pygame.image.load('seolean.gif')

map_width, map_height = map_img.get_rect().size

npcs = [NPC(400, 600, "Hello!", "seolean.gif"), NPC(900, 600, "Stay on your guard.", "guard.png"), NPC(500, 1100, "I am doing archery, watch out for my arrows!", "archer.png")]

def detect_collision(px, py):

for k,i in enumerate(npcs):

if i.x - 24 < px < i.x + 24 and i.y - 48 < py < i.y + 48:

return k

return -1

x = 0

y = 0

px = 0

py = 0

f = 2 # 0 up, 1 right, 2 down, 3 left

show_text = False

npc_text = ""

pygame.font.init()

myfont = pygame.font.SysFont('Comic Sans MS', 30)

while True:

for event in pygame.event.get():

if event.type == pygame.QUIT:

break

keys = pygame.key.get_pressed()

coll = -1

if keys[pygame.K_LEFT]:

coll = detect_collision(px-5, py)

if coll == -1:

px -= 5

px = max(0,px)

f = 3

show_text = False

if keys[pygame.K_RIGHT]:

coll = detect_collision(px+5, py)

if coll == -1:

px += 5

px = min(px,map_width)

f = 1

show_text = False

if keys[pygame.K_UP]:

coll = detect_collision(px, py-5)

if coll == -1:

py -= 5

py = max(0,py)

f = 0

show_text = False

if keys[pygame.K_DOWN]:

coll = detect_collision(px, py+5)

if coll == -1:

py += 5

py = min(py,map_height)

f = 2

show_text = False

if coll >= 0:

show_text = True

npc_text = (npcs[coll].txt)

x = max(0, min(px - int(width/2), map_width-width))

y = max(0, min(py - int(height/2), map_height-height))

screen.blit(map_img, (0, 0), area=(x, y, width, height))

screen.blit(pl_img, (px-x, py-y), area=(0, f*50, 48, 48))

for i in npcs:

screen.blit(i.sprite, (i.x-x, i.y-y), area=(0, 100, 48, 48))

#print("px: {}, py: {}, f: {}".format(px,py,f))

if show_text:

pygame.draw.rect(screen, [136,136,136], pygame.Rect(100,520,600,75))

text_surface = myfont.render(npc_text, False, (0, 0, 0))

screen.blit(text_surface, (105,525))

pygame.display.flip()

time.sleep(20 / 1000)

And a working Repl is here.

Read more...

As a resident in the UK, the whole Brexit soap opera is ubiquitous in my life. Now, up until recently, many Britons were hoping and campaigning for a second referendum (e.g., the now internally conflicted People's Vote movement). But when the Tory prime minister was about to ram through his deal with the help of a few loose cannon Labour members, and after another Brexit extension was granted, the Scottish National Party and the Liberal Democrats decided to offer support for holding a general election.

Re-rolling the dice for free

Both parties came to the realization that Brexit was imminent, and it was going to be pushed by a prime minister who received 80,000 votes to secure his position, and a witheringly dithering leader of the opposition, whose party appears to hold a randomly generated view towards Brexit in every other news article. So they rolled the dice.

The move makes sense from their perspective, but we do now have the issue that:

  • >1 million people hit the streets multiple time demanding a Brexit referendum.
  • We get to choose parties, not Brexit choices.

Is this election about Brexit?

It's a good question, and the answer lies in the newspapers. Just look at the coverage from the major newspapers in the past month, and you'll see that Brexit dominates the headlines. This is because it has a huge effect on the UK economy, on the security and stability of the country, of the freedom of movement of its people, and a huge number of other things involving food security, science and trade, for instance.

So any party claiming the election is not about Brexit is lying. But one could argue that the election should be not only about Brexit.

Now, in this blog post, I am assuming that the election is only about Brexit.

So assuming that, how should we vote?

Well, first figure out your Brexit preference, and then consult the table below:

Now this diagram does not include the parties in Northern Ireland (where Sinn Fein and UUP are Remain, and DUP is Leave), nor does it contain some of the smaller parties like UKIP. But I'm happy to make a separate one for NI if people would like me to.

But for Scotland, Wales and England, there are 4 options that are clearly in favor of revoking article 50. Tactical voting should be easy there, as the Greens, Plaid Cymru and the Lib Dems have allied up, and will provide a combined candidate in many crucial places. As for Scotland, I suppose Lib Dems or SNP would be your party of choice.

For all other option, the choice should be clear I think :).

One more thing: it could be that some parties shift position during the campaign in the coming weeks. If that is the case, then I'll be sure to update this diagram.

So what will happen during the election?

The campaign has only just started, so any forecasts at this time are likely to be extremely inaccurate. However, I do expect there to be major poll shifts during the campaign, as the stakes are high and gaffes are commonplace these days.

So, if you plan to vote tactically, you may want to stall your tactical decision for a while until the balance of power becomes a bit clearer.

Read more...

Today, a follow-up on the list of microscopic office crimes. The first part was not so well received, so I'll keep this part short and sweet, pad it with a bit of amusement, and not bother too much with any further follow-ups on this in the near future :).

The Hatchet Job: providing comments which only suggest what's wrong

“Your effort is admirable, but I cannot discern a scientific contribution and your methodology is inappropriate, lacks robustness and novelty.”. Image courtesy of *Pixabay*.

You send in an article, or report, only to receive in return something resembling a so-called hatchet job. Sometimes it's a review with paragraphs of intimidating roast-prose, and sometimes it's a face-to-face meeting (or viva) that is turned into a living hell.

Now there is nothing wrong with negative criticism, even if it is intensely delivered (!). However, vague criticism is a big issue, because you won't have the slightest clue what to do better when receive it. And as far as I'm concerned, non-constructive criticism is vague, simply because it doesn't contain a clear explanation on what you should be doing instead.

Unfortunately, this kind of criticism is endemic in academia. In fact, I personally have seen it more often at top institutions than in mid-Tier ones, and more often with senior academics than junior ones. Perhaps people just get fed up with trying to be helpful, and decide to join the hatchet brigade instead?

The Snob: ask help for a problem you can personally solve easily.

“Do you know where my plate is?”. Image courtesy of kremlin.ru.

Now we already covered the fact that delegating is good, but delegating tiny tasks not so much. Likewise, when you are solving a problem, it's good to call in assistance when you deem it necessary. It's not so great when you call in assistance when it clearly is not necessary.

To be honest, I frequently stumble over this one, for example because I tend to lose track of vital but trivial pieces of information amongst my horde of e-mail and reference documents. But the list is much longer than that! Micro-crimes in this category could for example include:

  • Asking for a template that someone has already sent to you (I plead guilty!).
  • Asking for help solving a programming error without at least having Googled it.
  • Asking for support on a tiny problem, just because the person in question happens to sit next to you in the office.
  • Asking for help on solving a technical issue “out of principle”, just because you decided to use a wrong/faulty/impractical software platform.
  • Barging into a person's office with your trivial and not-so urgent support request, while an e-mail would have clearly sufficed.

Sometimes I wonder whether people commit these kind of micro-crimes because they like to have more personal contact in the workplace. If so, I have a solution: just to take your colleagues out for a coffee or a pint!

The Talk Show Multi-Millionaire: credit a person who has not done anything

https://www.youtube.com/watch?v=XcI-rHO0yko&feature=youtu.be

“You get a car, and you get a car... you all get a car!!!”. Image courtesy of the Oprah Winfrey Show.

(ugh, I now catch myself typing “Oprah Winfrew” for the third time today while importing this image! Never mind, moving on...)

Papers, software, reports and proposals, they all require a lot of work. You and many your colleagues can easily spend days, weeks or months completing them. And as a reward you get to be an author or contributor!

Then there is that occasional colleague who ends up on the author list with 15 minutes of effort because he had that killer idea, that crucial critique, or he just happened to have funded your position. They too, get to be an author or contributor, that's fair game right?

(depends on the journal/research culture I suppose)

But then there are these people who end up being credited with even less than that. For instance, they may have ascended to such lofty levels that the very presence of their name alone supposedly imbues excellence on any piece of scientific research. Those who get the sympathy bonus for having the privilege of being in your physical office proximity. Or those who haven't contributed anything, but you are too scared to let go...

It's all very much understandable, but not quite correct conduct of course.

Closing thoughts

That's it! No more office micro crimes for a while! Unless you all start to really insist of course...

For subscribers I have a little bit about why my posting has been a bit infrequent as of late.

Read more...

Today I focus on making a walkabout engine, so that you can walk around your self-created game world. It's a return to a technical post after a few design-oriented ones about world shapes. I will start very simple here, but in future tutorials I'll be sure to add more layers and details, as we work towards a fully functioning game. To do this, I use PyGame and Repl.it, because with a bit of wobbling I actually managed to get a semi-smooth engine working.

Whenever I think of a walkabout engine, I always have to think back to the first RPG I got truly hooked on, hence the header image ;).

Initialising the Walkabout Engine

Okay, time to get going. To start this off, we first need to create a PyGame Repl, which is explain in my previous post here. Then, we need to initialise PyGame, and create a display with a background color. You can do this with the following 7 lines of code:

import pygame

import time

pygame.init()

width, height = 800, 600

backgroundColor = 0, 0, 0

screen = pygame.display.set_mode((width, height))

screen.fill(backgroundColor)

Next up, we need to load a map image of our choice:

map_img = pygame.image.load('Equirectangular_projection_SW.jpg')

(I uploaded the file and use the example from the first Game World tutorial.)

And store its height and width in pixels for later use.

map_width, map_height = map_img.get_rect().size

We also define four variables:

x = 0

y = 0

px = 0

py = 0

Here, x and y are the base map coordinates of the top left corner of the viewing screen, and px and py are the coordinates where our player resides.

The Main Loop

In the main loop, we will be doing something new, because today's walkabout demonstrator is going to be interactive. You will be able to use the arrow keys to move around your character (which I represent as a circle) across the map.

To do this, let's define the loop:

while True:

And use PyGame's event handling system to figure out which key has been pressed:

for event in pygame.event.get():

if event.type == pygame.QUIT:

break

keys = pygame.key.get_pressed()

Now keys will contain a dictionary of 0s and 1s, but we now need to extract each key from that dictionary and check it. We start doing that with the left key:

if keys[pygame.K_LEFT]:

px -= 5

px = max(0,px)

Now what do I do here? In the first line I check whether the Left arrow key has been pressed. If so, then I move the player 5 pixels to the left (2nd line) and set the x coordinate to 0 if the player happens to move off the map on the left side (px<0).

Next, we repeat this exercise for the right key:

if keys[pygame.K_RIGHT]:

px += 5

px = min(px,map_width)

, which is identical, except that we set the x coordinate to the map_width if the player happens to move off the map on the right side (px>map_width).

Next, we add similar handling for the up and down keys:

if keys[pygame.K_UP]:

py -= 5

py = max(0,py)

if keys[pygame.K_DOWN]:

py += 5

py = min(py,map_height)

Now that we have the player coordinate correctly, we need to calculate where the camera should be positioned:

x = max(0, min(px - int(width/2), map_width-width))

y = max(0, min(py - int(height/2), map_height-height))

In a nutshell, we essentially want the player to be in the center (px - int(width/2)), but we also don't want to show parts beyond the edges of the mape (hence the max and min functions).

Lastly, we render the map:

screen.blit(map_img, (0, 0), area=(x,y,width,height))

Then draw the player, so it ends on top of it:

pygame.draw.circle(screen, (255, 255, 255), (px-x, py-y), 15, 0)

And finally, print the player coordinates for convenience, display our graphics, and sleep for 25 ms, which translates to a maximum of 40 frames per second:

print("px: {}, py: {}".format(px,py))

pygame.display.flip()

time.sleep(25 / 1000)

And with all that, if you provided the right image, you should get something like this:

Closing thoughts

We're building our Game World with baby steps, but this time around we can finally traverse the map. As usual I've attached the source code and a Repl for your convenience. Also, for subscribers I've added a small example featuring an animated character.

However, if you're not a subscriber don't worry, as we will be touching upon animated character movement soon enough! :)

Appendix 1: Full Game Code and Repl

Here's the full source code:

import pygame

import time

pygame.init()

width, height = 800, 600

backgroundColor = 0, 0, 0

screen = pygame.display.set_mode((width, height))

screen.fill(backgroundColor)

map_img = pygame.image.load('Equirectangular_projection_SW.jpg')

map_width, map_height = map_img.get_rect().size

x = 0

y = 0

px = 0

py = 0

while True:

for event in pygame.event.get():

if event.type == pygame.QUIT:

break

keys = pygame.key.get_pressed()

if keys[pygame.K_LEFT]:

px -= 5

px = max(0,px)

if keys[pygame.K_RIGHT]:

px += 5

px = min(px,map_width)

if keys[pygame.K_UP]:

py -= 5

py = max(0,py)

if keys[pygame.K_DOWN]:

py += 5

py = min(py,map_height)

x = max(0, min(px - int(width/2), map_width-width))

y = max(0, min(py - int(height/2), map_height-height))

screen.blit(map_img, (0, 0), area=(x,y,width,height))

pygame.draw.circle(screen, (255, 255, 255), (px-x, py-y), 15, 0)

print("x: {}, y: {}".format(x,y))

pygame.display.flip()

time.sleep(25 / 1000)

And here's the Repl.it link.

Read more...

Read more...

Today marks exactly two months since I joined Coil, and certainly it has been two very different months! After month one I posted a fairly upbeat review, with a lot of ideas on how to improve the blog. A day later I even optimistically decided to commit to the 30 Day Blogging challenge. And then, work and life largely caught up with me.

7 posts (8 including this one)

Overall, I tallied up 8 posts over 30 days, which is 22 short of the target. Immediately after I accepted the challenge, I realized two things: (1) the obligation to post killed all my creativity, and (2) it's insane to try and double/triple your post rate while at the same time entering teaching term, which is traditionally a busy work period in the year.

And there is a third point too: I really don't want to compromise on quality by rapidly cranking out half-baked blog posts. Overall, my hope is that many of my blog posts are not only useful when they are freshly in the Coil feed, but that many of them can still serve as a useful reference 6, 12 or 60 months later.

Traffic & Categories

Image courtesy of *publicdomainvectors.org*.

It's good to be honest: my blog has received about 75% less Coil subscriber traffic in the second month than in the first. This may be because I've had fewer posts (8 vs. 12 in the first month), because people get the hang of my blog and what it is about, or perhaps because many of my later posts are more obscure than my earlier ones. An alternative explanation is that the blogging quality at Coil has improved quite a lot, and that my posts are simply up against a lot more competition! ;)

However, I suspect that my blog actually got more non-subscriber traffic, in part because I advertised it more widely in the second month (through Facebook and Twitter), and because I received a lot *more* responses to my blog posts through social media (Twitter, Facebook, Reddit).

Now let's have a quick overview of the categories:

  • Small Sims: 7 posts, 28 upvotes.
  • Game World: 3 posts, 12 upvotes.
  • Boffin's Office: 2 posts, 2 upvotes.
  • Blast from the Past: 1 post, 1 upvote.
  • General new content posts: 3 posts, 19 upvotes.
  • Posts about the blog, or other meta posts: 5 posts, 15 upvotes.

The picture here is clear: the Small sims series works well (except the pub model ;)), the Game World series works well, and in particular the general content posts (e.g., about programming languages) draws a big crowd. The Blast from the Past and Boffin's Office series haven't taken off yet, but then again I only made very few posts in either series.

Onwards to Month 3!

In the coming month I will still be very busy work-wise, so I'll keep it steady and modest in terms of contributions. I'd like to crank out a few more Small Sims and Game World posts, and I'll probably add a Boffin's Office and a Blast from the Past post as well. But, having just done the analysis of my previous posts, I will also try to add in 1 or 2 extra general content posts this time!

Don't see this as a promise, but as an ambition though.

Lastly, for the subscribers, I'll have a Coil Coffee Rave below.

Credits

Header image courtesy of Mike Haller.

Read more...

Having discussed my take on meetings, I'd like to focus this second installment of Boffin's Office on microscopic office crimes. Like most things micro, they don't make the world fall apart, but they can be profoundly wasteful and lead to unnecessary tensions in the workplace.

I keep this list as a reminder to myself what not to do. Of course that doesn't mean I never did any of these things: In fact the whole point of this reminder list is exactly because I tend to forget about these issues ;).

Anywhere, here goes:

Delegate tiny tasks

“Could you forward this two-line e-mail to Jimmy, please?” Source: pixabay.

As a supervisor, I am encouraged by many other supervisors to delegate, delegate, delegate. After all, if you have multiple people working for you, delegating means that you can get multiple tasks finished at the same time.

Why could this be bad? Well, delegating doesn't come for free. First, you have to request someone to do the particular task, and monitor the progress and outcome. And second, the person you delegate to might or might not like the task you've just delegated.

Now I used to get very annoyed whenever I was given the responsibility over a task that literally takes a minute or two to finish. Whereas the supervisor might think that this task is nice and easy, the one being supervised is much more likely to wonder why the line manager couldn't just do that task her- or himself. So, now that I have a supervisor role, I need to avoid delegating such tiny tasks (unless they are repeating of course).

Perverse variation: Delegate a tiny task, then do it yourself after all and ignore any input you received from others. Oh, and make them feel them guilty for not responding promptly enough. I wouldn't mention it if I hadn't experienced it ;)..

Requiring confirmations for appointments

In the UK, it is almost impossible to stick to this one, as half the country enjoys making an appointment, only to then get back to you weeks later to check and confirm, once again, whether the appointment still stands. You had better respond that time, otherwise the appointment will be lost.

Coming from a Dutch background, I wasn't used to this practice at all: an appointment made is an appointment confirmed. At first I thought it was because others didn't trust me to stick to the appointments I made. However, I soon discovered that it had little to do with the trust in me, and all the more with people's general assumptions that colleagues run a sloppy agenda.

Why could this be bad? Well, it roughly doubles all incoming messages that are about appointment-making, which is about half of what my inbox consists of. Moreover, it encourages people to run a sloppy agenda, as if an appointment not confirmed twice isn't really an appointment at all. Lastly, it doesn't exactly exude trust in the other party...

Worst of all, imagine when the era dawns that people start fading on confirmed appointments. Are we then going to move to double-, triple- or quadruple-confirmed appointments?

Perverse variation: Double-confirm an appointment, and then not show up yourself. I've had this happen once to me, and I must admit having committed this crime once as well, to my own shame.

Send a person on holiday an e-mail about non-critical work things

“Just in case you started enjoying yourself...”. Courtesy of svgsilh.com and *pxhere.com*.

We all get them: you're away on a trip, make the mistake of checking your inbox, and realize the pile of screaming e-mails telling you to paint the new bicycle shed. Very often, the blame here falls on the receiver (”Don't check your inbox while on holiday!”), but I prefer to focus on the sender here.

Why could this be bad? People take holiday to spend quality time with family and friends, to recover from the strain caused by work, or simply because they deserve it. It's therefore a very bad time to send messages.

On the rare occasion that I do send them, I try to first determine whether the recipient would want to receive a particular message in the middle of holiday, given the mental disruption it causes?

The answer is generally no, except if the person on holiday would miss out on something very valuable (e.g., a grant, contract extension, accepted publication or trip they're already keen to make) if I were not to contact them, and very little action is required from their side.

Perverse variation: So you're bothered by e-mails? How about WhatsApps or worse still: phone calls on your mobile? :)

Imposing overly early deadlines

Courtesy of *pxhere.com*.

You need to send out that 10-page report by the 20th, so ask for input by the 13th. But what if you fear that the others will not abide the deadline?

Indeed, one habit I come across in academia is people imposing overly early deadlines on others. In an attempt to prevent themselves from being overloaded with last-minute work, they request others to contribute earlier and earlier, irrespective of their workloads, with a built-in assumption that they won't be on time anyway.

Why could this be bad? Well, it (a) exudes a lack of trust and (b) forces the people around you to prioritize the work you give earlier than needed, simply because you don't trust them.

The solution? Well, you could just trust them to deliver by the original deadline... Or, you could make a spreadsheet to track the contributions and invite the contributors to set their own deadlines in the spreadsheet, being aware of your final deadline.

Perverse variation: I have been confronted a handful of times with people who impose deadlines on me that were already in the past...

Closing thoughts

So this wraps up a first listing of microscopic office crimes. I'll probably do one more installment of this later on, and possibly if people enjoy reading about these ;).

For the subscribers I'll provide a link to some (cringe?)worthy content arguing the opposite viewpoint on appointment confirmation...

Credits

Header courtesy of Alexas Photos (pixabay.com).

Read more...

Election is imminent in the UK, and there are more an more discussions about the voting system used here. How does a first-past-the-post election work in practice, and how does it compare to an election with proportional representation? Now instead of providing swathes of explanation, I'm going to invite you today to just code the whole thing up in a simulation :).

So our mission today is:

  • to model a random election.

To start off this tutorial, simply go to Repl.it and create a new Python Repl (not PyGame, because we don't need animations/graphics today.

What libraries do we need today?

I am keeping it simple: we need to randomize stuff:

import random

and because of a particularly bizarre bug with 2D arrays on replit (which has been labelled unreproducible on Stack Overflow but which I luckily could reproduce :P), we will need numpy for part of the code to wield proper arrays:

import numpy as np

Defining the election

In an democratic election, we have people, who live in constituencies (also known as districts) and vote on parties, which then occupy seats. So, to begin we need to define how much we have of each of these four nouns:

number_of_voters = 100000

number_of_parties = 4

number_of_constituencies = 50

number_of_seats = 100

I choose to have 100,000 votes, 4 parties, 50 constituencies, and 100 seats.

Defining the voters

Every voter in my election votes on a random party and lives in a randomly picked constituency out of the 50 available. We code that up as follows:

votes = []

constituencies = []

for x in range(0, number_of_voters):

v = random.randint(0, number_of_parties - 1)

votes.append(v)

constituencies.append(random.randint(0, number_of_constituencies - 1))

Comparing votes

Now in this study we will compare votes between the 4 parties many times, and we need a simple function to decide which party has the highest vote count. We do this using the following:

def find_highest_vote(votes_by_party):

highest_votes = -1

highest_vote_party = 0

for k,v in enumerate(votes_by_party):

if v > highest_votes:

highest_vote_party = k

highest_votes = v

return highest_vote_party

This function takes a Python list named votes_by_party, which has one element for each party (so 4 elements in our case). We then compare the totals between the elements, and then return the index number of the element that has the highest count (i.e. the number of the winning party).

Image courtesy of *Agencia Brasil*.

Now the simplest thing to do first is to just count the votes. Given that people vote randomly, how many votes does each party end up with?

We count votes by going through the votes cast, and tallying them up for each party as follows:

# Popular vote counting

party_votes = [0] * number_of_parties

for v in votes:

party_votes[v] += 1

Next, we simply print the votes registered for each party as follows:

print("\nElection result - total vote count")

for k,p in enumerate(party_votes):

print("Party {} - votes {}".format(k, p))

Counting the votes: proportional representation

In proportional representation, we make as direct a mapping from votes to seats as we can, and entirely ignore constituencies. You can find this election system in countries like the Netherlands and Israel, for instance. It is much criticized for creating a large number of small parties across the spectrum, and praised for being able to represent all votes with near-equal importance.

First we create two data structures to store the votes and the seats counts for each party:

print("\nElection result - Proportional Representation")

pr_party_votes = party_votes.copy()

pr_party_seats = [0] * number_of_parties

Next, we calculate how many votes a single seat in Parliament represents:

votes_per_seat = number_of_voters / number_of_seats

Now, we start a loop that keeps going until all seats are filled:

for i in range(0, number_of_seats):

...and in this loop we first find every time which party has the most votes:

highest_vote_party = find_highest_vote(pr_party_votes)

...that party then gains a seat, but loses votes_per_seat votes, as a seat has been taken:

pr_party_seats[highest_vote_party] += 1

pr_party_votes[highest_vote_party] -= votes_per_seat

Once that loop has completed, all the seats are filled. The last thing we will want to do (outside of the loop again) is to simply print out how many seats each party got:

for k,p in enumerate(pr_party_seats):

print("Party {} - seats {}".format(k, p))

Counting the votes: first past the post

The first past the post system checks who wins in each constituency, and then assigns one or more seat to that constituency. It is used in the countries that are marked red on this map:

Courtesy of Wikimedia Commons user Jiovigo.

For first past the post, we start in a similar way. We define the seats by party, but now we also have to tally up the votes separately for each constituency, so we make a record of votes by constituency and by party:

print("\nElection result - First past the post")

fptp_party_seats = [0] * number_of_parties

votes_by_constituency = np.zeros((number_of_constituencies, number_of_parties))

We then tally up the votes, placing each voters preference in the tally for their respective constituency:

for k,v in enumerate(votes):

votes_by_constituency[constituencies[k],v] += 1

Now that all votes have been recorded, we need to convert the constituency votes into seats. Because we have 100 seats and 50 constituencies, we are going to rule that the winner in each constituency wins two seats:

for c in votes_by_constituency:

highest_vote_party = find_highest_vote(c)

fptp_party_seats[highest_vote_party] += int(number_of_seats / number_of_constituencies)

And with that done, all that remains is print out the results!

for k,p in enumerate(fptp_party_seats):

print("Party {} - seats {}".format(k, p))

Now if you did everything correctly, you can press the Play button and will get a result similar to this:

... or perhaps you get something very different! The code is stochastic (uses a randomizer), so every time you run it, you will almost certainly have a slightly different outcome :).

Closing thoughts

As you may have noticed, the first past the post system can give wildly variable outcomes, even if the total number of votes per party is very similar. This is not strange at all, as even an event as recent as the 2016 USA election resulted in the losing presidential candidate raking up more votes than the winner.

You can play around with the different parameters at the start of the tutorial to see how the systems behave in different circumstances. There are just a few aspects that you need to be mindful of:

  • If you increase the number of voters beyond a few million, you may notice that repl.it will start to struggle to run the simulation (this is browser and machine dependent though).
  • Make sure that the number of seats is an exact multiple of the number of constituencies, otherwise the first past the post code might break :).

With that being said, feel free to explore as you like! For your convenience I provide the source code and Repl below. And for subscribers I have a small tidbit about voting thresholds.

(Back to Small Sims Index)

Credits

Header image courtesy of Wikimedia Commons.

Appendix: Source code and Repl

The source code of my election simulation is here:

import random

import numpy as np

def find_highest_vote(votes_by_party):

highest_votes = -1

highest_vote_party = 0

for k,v in enumerate(votes_by_party):

if v > highest_votes:

highest_vote_party = k

highest_votes = v

return highest_vote_party

number_of_voters = 100000

number_of_parties = 4

number_of_constituencies = 50

number_of_seats = 100

votes = []

constituencies = []

for x in range(0, number_of_voters):

v = random.randint(0,number_of_parties-1)

votes.append(v)

constituencies.append(random.randint(0,number_of_constituencies-1))

# Popular vote counting

party_votes = [0] * number_of_parties

for v in votes:

party_votes[v] += 1

print("\nElection result - total vote count")

for k,p in enumerate(party_votes):

print("Party {} - votes {}".format(k, p))

print("\nElection result - Proportional Representation")

pr_party_votes = party_votes.copy()

pr_party_seats = [0] * number_of_parties

votes_per_seat = number_of_voters / number_of_seats

for i in range(0, number_of_seats):

highest_vote_party = find_highest_vote(pr_party_votes)

pr_party_seats[highest_vote_party] += 1

pr_party_votes[highest_vote_party] -= votes_per_seat

for k,p in enumerate(pr_party_seats):

print("Party {} - seats {}".format(k, p))

print("\nElection result - First past the post")

fptp_party_seats = [0] * number_of_parties

votes_by_constituency = np.zeros((number_of_constituencies, number_of_parties))

for k,v in enumerate(votes):

votes_by_constituency[constituencies[k],v] += 1

for c in votes_by_constituency:

highest_vote_party = find_highest_vote(c)

fptp_party_seats[highest_vote_party] += int(number_of_seats / number_of_constituencies)

for k,p in enumerate(fptp_party_seats):

print("Party {} - seats {}".format(k, p))

And you can find the Repl here.

Read more...