Friday, February 1, 2019

Pokemon Go Super Effective Attacks by Type

Pok
The data for this
The above flowchart shows only the "Super Effective" attack types -- adding the "Weak" attacks makes a pretty absurd looking flowchart.

However, if you are choosing a Pokemon type that will be Super Effective for a fight, you also want to know what the effectiveness of the opponent will be against you. Because if type A is Super Effective against type B, then type B effectiveness against type A might be Normal, Weak, or Extremely Weak. This is represented by the color and thickness of the lines.

  • Thin, pure red lines: effectiveness of the reverse attack is normal
  • Thicker, somewhat purple lines: effectiveness of the reverse attack is weak 
  • Thickest, dark maroon lines: effectiveness of the reverse attack is extremely weak
So if your opponent is type Psychic, your best option is to choose a Pokemon with type Dark.


The data for this came from https://pokemongo.gamepress.gg/pokemon-go-type-chart. In text form, the full data is

  • Bug does 1.6x damage to: Dark, Grass, Psychic
  • Bug does .625x damage to: Fighting, Fire, Flying, Fairy, Ghost, Poison, Steel
  • Dark does 1.6x damage to: Ghost, Psychic
  • Dark does .625x damage to: Dark, Fighting, Fairy
  • Dragon does 1.6x damage to: Dragon
  • Dragon does .625x damage to: Steel
  • Dragon does .391x damage to: Fairy
  • Electric does 1.6x damage to: Flying, Water
  • Electric does .625x damage to: Dragon, Electric, Grass
  • Electric does .391x damage to: Ground
  • Fairy does 1.6x damage to: Dark, Dragon, Fighting
  • Fairy does .625x damage to: Fire, Poison, Steel
  • Fighting does 1.6x damage to: Dark, Ice, Normal, Rock, Steel
  • Fighting does .625x damage to: Bug, Fairy, Flying, Poison, Psychic
  • Fighting does .391x damage to: Ghost
  • Fire does 1.6x damage to: Bug, Grass, Ice, Steel
  • Fire does .625x damage to: Dragon, Fire, Rock, Water
  • Flying does 1.6x damage to: Bug, Fighting, Grass
  • Flying does .625x damage to: Electric, Rock, Steel
  • Ghost does 1.6x damage to: Ghost, Psychic
  • Ghost does .625x damage to: Dark
  • Ghost does .391x damage to: Normal
  • Grass does 1.6x damage to: Ground, Rock, Water
  • Grass does .625x damage to: Bug, Dragon, Fire, Flying, Grass, Poison, Steel
  • Ground does 1.6x damage to: Electric, Fire, Poison, Rock, Steel
  • Ground does .625x damage to: Bug, Grass
  • Ground does .391x damage to: Flying
  • Ice does 1.6x damage to: Dragon, Flying, Grass, Ground
  • Ice does .625x damage to: Fire, Ice, Steel, Water
  • Normal does .625x damage to: Rock, Steel
  • Normal does .391x damage to: Ghost
  • Poison does 1.6x damage to: Grass, Fairy
  • Poison does .625x damage to: Ghost, Ground, Poison, Rock
  • Poison does .391x damage to: Steel
  • Psychic does 1.6x damage to: Fighting, Poison
  • Psychic does .625x damage to: Psychic, Steel
  • Psychic does .391x damage to: Dark
  • Rock does 1.6x damage to: Bug, Fire, Flying, Ice
  • Rock does .625x damage to: Fighting, Ground, Steel
  • Steel does 1.6x damage to: Fairy, Ice, Rock
  • Steel does .625x damage to: Electric, Fire, Steel, Water
  • Water does 1.6x damage to: Fire, Ground, Rock
  • Water does .625x damage to: Dragon, Grass, Water


Thursday, June 28, 2018

Minecraft Reference

Will try to fill in more details later, but for now these are the most useful reference info-graphics I have seen for mastering Minecraft.

Mob spawning ranges:


Villager trading chart:


Brewing chart:


Stronghold distances:


How to respawn the Ender dragon:



And just for fun, here's the "Link" skin I used for my character:

Thursday, February 8, 2018

Podcasts

Why Leaders Eat Last -- by author Simon Sinek

A few great episodes from This American Life:
452 Poultry Slam 2011
540 A Front
544 Batman

An intelligent podcast discussion of government surveillance.
from the podcast "Berkman Center for Internet and Society"
http://feedproxy.google.com/~r/audioberkman/~3/c76sSH9bQv8/
Duration 1:29h, Published 4/10/14 3:45:38 PM

A good podcast on morality - analyzing what feels right or wrong instinctively, how we sometimes override that, and the conflicts between different people groups' moral codes
http://kuow.org/post/why-can-t-we-all-just-get-along

FISA courts - everything you might have wanted to know. An interview with Dave Opderbeck of Seton Hall Law School. This is from 2012, I just happened to find it now, but I do not think anything has changed since then.
http://cyberlaw.stanford.edu/podcasts/20130702-Levin-187-Opderbeck.mp3
I heard this on the "Hearsay Culture" podcast - http://www.hearsayculture.com/?feed=rss2

Bees practice Democracy better than humans...
http://podcastdownload.npr.org/anon.npr-podcasts/podcast/5194672/136636626/npr_136636626.mp3 (3 MB)

Excellent podcast episode from Planet Money, with two stories that explain the idea of "quantitative easing" (printing money) as a fix for a recession.
http://podcastdownload.npr.org/anon.npr-podcasts/podcast/510289/291533350/npr_291533350.mp3 (9 MB)

A good talk that includes some information I didn't know, like how the Wright brothers went around enforcing their patents, keeping American aviation held back until they were basically stopped by congress.
http://archive.mises.org/18290/open-science-versus-intellectual-property/

Do you want work to be more fulfilling, fun, or just less defective? Check out this interesting podcast on how WordPress does it.

Stack Exchange Podcast #23 - about gamification, used for good and evil, addiction and the Skinner Box, and more

Radiolab: Mischel's Marchmallows - about self-control and how it was discoverd to be a very strong predictor of success

SE-Radio Episode 176: Quantum Computing - a good overview of what quantum computing is, why it is being pursued, and how far along we are

SE-Radio Episode 79: Small Memory Software - strategies for coping with small memory requirements, presented in a clear and simple way.

SE Radio Episode 88: Singularity - a Microsoft research operating system, with some really cool sounding features that allow secure code. If only Microsoft would implement more of them in its commercial products.

An excellent overview of copyright - the reasons, history, and controversy.

Bath podcast: GULP Maths and the Making of the Modern World
If you've ever asked "what good is mathematics", check it out.

A look "at how particle physics has developed...and what developments we might expect." Pretty much a walkthrough of particle physics from atoms to current theory. Remember that quantum mechanics is the dream stuff is made of, so this is a dream interpreter. =-]

IT Conversations - Clay Shirky on Technology Insight
itc.conversationsnetwork.org/shows/detail4411.html
An insightful talk on "contracts with the users", and how freedom to create is a vital element of social media projects. No words wasted, a good listen.

Cory Doctorow: The Coming War On General Purpose Computation
or if you must do youtube - youtu.be/HUEvRyemKSg
The best explanation of computers and the problems of their legislation that I have ever heard, 5 stars! Covers the problem, in context, in a way that everyone can understand. It is a bit long, but worth it; only the first 30 minutes is the original speech, then there is Q&A for 20 minutes.

Amy Jo Kim: Beyond Gamification: 7 Core Concepts to Create Compelling Products
itc.conversationsnetwork.org/shows/detail4870.html
A reluctant favorite - gamification is overhashed and not that subtle, but this really is a good clean overview with clear, specific points. If I ever want to apply gamification, I will probably come back and listen to it again.

Really just the part about time tracking and "evidence based scheduling", which can be read in the transcript: https://stackoverflow.fogbugz.com/default.asp?W29122

The IP Colloquium - Tim Wu and The Master Switch
ipcolloquium.com/mobile/2010/12/wu/
An interview/conversation about the rise and consolidation of phones and cell phones, how regulation impacts innovation; and speculation on the internet and network neutrality.

Surprisingly Free - "Nathaniel Gleicher on the Stored Communications Act and the need for reform"
surprisinglyfree.com/2010/03/22/nathaniel-gleicher-on-the-stored-communications-act-and-the-need-for-reform/
Discussion of how users are tracked as they browse the web, the Stored Communications Act, the protections of the 4th Amendment, and the need for reform. This is an overview of how the laws are currently interpreted, and the implications (not that I in any way agree with those interpretations).

Thursday, May 4, 2017

Terra Mystica - 6-7 Player Variant

Number of Rounds

With 6 players, reduce the number of rounds from 6 to 5.
(With 7 players, you may want to reduce it even to 4 rounds.)

Reason: Having 6 or 7 players will make the game take too long for most people's comfort.
Note: If you do choose to play 6 rounds, you may want to also remove the variation rules for Structures.

Structures

Remove some structures in the setup. The structures you remove go in the box and cannot be built in the game, but leave empty spaces on the Player Boards, so they will generate income each round.
  • With 6 players every player remove 3 Dwellings, 1 Trading Post, and 1 Temple.
  • With 7 players every player remove 4 Dwellings, 2 Trading Posts, and 1 Temple.
  • Since you remove a Temple during Setup, take a Favor Tile when you place your first structure. So the first player select the first Favor Tile, proceeding in turn order. Chaos Magicians do take 2 Favor Tiles, but since they always place structures last, they will choose their Favor Tiles last as well.
  • In the Income Phase of each round, players receive extra income due to the buildings removed and from Favor Tile(s) selected.
  • Structures removed do not give Victory Points for building, nor count as "ownership" of these structures.

Reason: More players will leave less open spaces on the map for building structures, so less structures are needed for each player.. Additionally, reducing the number of rounds from 6 to 5 reduces the overall resources acquired during the game; so increasing resources gained by removing these structures from your board offsets this.
Note: An optional rule even in the base game, with 5 players every player may remove 1 Dwelling and 1 Trading Post.

Cultist Board

If the Cultist Board has all four positions of an element occupied, you may still move your cultist to that element on the Cult Board by paying to remove one of those currently occupying a space.
  • Pay a number of coins equal to the value of the space (2 or 3) to the opponent whose Priest you are removing.
  • You may only remove a Priest from an element track when your Action is to move one of your Priests to the same track, and when that track already has all four positions occupied.
  • You may not remove a Priest from an element that you currently have a Priest on.
  • The opponent's priest is removed from the game (move it to the game box, not back to the player's Priest pool).
  • Once you have paid to remove the opponent's Priest, place your Priest and advance your marker on the Cult Track as normal. The opponent's marker does not move down when their Priest is removed.
  • As normal, you may still move 1 Priest to your pool to advance 1 space on the Cult Track.
Reason: To allow all players to be able to send a Priest to a Cult track.

Power Actions

Every Power Action can be used any number of times. The cost is 1 extra Power for each time the Power Action has already been used that round. Keep track of this by adding extra X tokens next to the action.

Reason: To allow more players to be able to use Power Actions.

Towns

There is no limit to the number of Towns that can be founded. If you found a Town and there are no Town Tiles left, you receive 5 Victory Points instead, as well as any other Faction bonus (Swarmlings receive 2 Workers, Witches 5 extra Victory Points). You do not receive a Town Tile, nor a Key.

Reason: The town limit is not reached in a normal game. Without these adjustments, it is very likely to reach the limit early on with 6 or 7 players, which does not make sense in the setting. It might also unbalance some factions to not be able to set up towns, especially the Chaos Magicians who are handicapped in the first round.
Comment: Normally there is a limit to 10 Towns, or 14 if you have the 4 extra Essen Town Tiles.

You may found a Town with only 3 connected structures if one of them is a Fortress, exactly like if one is a Sanctuary, but only if you have already built your Sanctuary. If you build a Sanctuary and already have a Fortress with 3 connected structures of sufficient value for a Town, you immediately found the Town under that Fortress.

Reason: Both the reduced number of Structures and the increased number of opponents makes it more difficult to gain 4 connected structures.

Scoring Tiles

With 6 players, in the Setup remove 3 Scoring Tiles of 3 different Cults.
With 7 players, in the Setup remove 4 Scoring Tiles of 4 different Cults.
ed: I need to read the rules again to see if this is worded correctly, because I'm not sure what this means as written.

Bonus Tiles

With 6 players use all the Bonus Tiles.
With 7 players use all the Bonus Tiles, and at the end of each Round place 2 Coins (instead of 1 Coin) on every Bonus Tile that was not chosen. (Or, if you have it, add the Spielbox Promo Bonus Tile instead).
Favor Tiles

With 6 players, the four 3-advance-Tiles can be taken twice. The first player that takes one of these tiles also gains 3 extra Power.
With 7 players, the four 3-advance-Tiles can be taken three times. The first player that takes one of these tiles gains 5 extra Power, the second gains 3 extra Power.
You cannot take a 3-advance-Tiles you currently own a second time (so if another player takes it from you, you can take it back).
To keep memory of the number of times that these Tiles were taken, put 1 Coin (or 1 Worker) on it each time it is taken.

Power Tokens

With 6 players, each player starts with 2 less Power tokens in Bowl I (5 tokens leftover).
With 7 players, each player starts with 3 less Power tokens in Bowl I (2 tokens leftover).
ed: I need to read the rules again to see if this is worded correctly, because I'm not sure what this means as written.

Improvise Other Limited Game Pieces

Some less important components may run out; just use improvised tokens/pieces, or adapt to not need them.
  • If more than 5 players exceed 100 Victory Points, write "100 Victory Points" on cards, or just write down players with over 100 VP on a piece of paper.
  • There are only 5 Rule Summary tiles, so either share 1 or 2 between two adjacent players, or copy them onto index cards.
  • Treat worker cubes and coins as if they are unlimited. If they run out, use some other tokens to represent them (Monopoly money, toothpicks, whatever).
  • If Terrain Tiles runs out, use something else to represent them.
    • Alternately, remove Terrain Tiles that are under an established Structure. The current terrain only serves to restrict what structures may be built, and to determine what the cost is to terraform a tile to another type of terrain. Therefore, once a structure is placed, the only purpose of leaving a terraforming Terrain Tile under it is to reassure the other players that the cost of terraforming has already been paid.
  • There are already enough Faction Boards, Structure pieces, and Action Markers for up to 7 players.
Credit

Originally from a post at goblins.net/downloads/terra-mystica-regolamento-italiano-faq-varianti, by Davide Malvestuto aka Principe Konrad on Jan 13th, 2013.
Translated by him into English on a post at boardgamegeek.com/article/15802476#15802476
Modified for this blog post at abamacus.blogspot.com

Monday, March 27, 2017

Universal Password Blacklist

One password-rule to rule them all: Universal Password Blacklist

When someone uses a password, it proves that it is not very "unique"; just being used once either proves it was already low-entropy, or makes it lower-entropy (because the user could have it written down, the system that accepted it might store it in the clear, etc). So no one, anywhere, ever, should be allowed to create an account with that password again.
How to you prevent a password from every being used again? Simple, create a public, universal blacklist for passwords. This one stroke, by itself, forces users to invent ever-more-entropy-laden passwords as time passes.
Of course, you need to do it securely. Which is the rest of this blog post.

Part 1:
My first thought (from a few years ago) is that when passwords are replaced (invalidated) in anyone's system, those passwords should be published, totally publicly.

When you change your password, you always have to enter your old password, and the new password. Also, when you delete an account, you usually have to enter your old password to do it. Either way, this transaction includes a "delete this password" component.
Some systems would automatically store this as a local blacklist of some kind, maybe just in the hashed form; but as far as I know, no one has tried to share these blacklists with other companies, and certainly no one publishes them to the world, which is basically what I am proposing.

If you're on board with black-listing these passwords from your system forever, why not publish them? They're no good anymore, so you could even store these now-obsolete password in the clear! (there are actually potential down-sides to storing in the clear, but since I remove this below, I won't go into them)

So send the now-blacklisted password to a giant repository in-the-sky (cloud). Get a bunch of companies to adopt this common system, and you are on your way to creating a Universal Password Blacklist. You could have a bunch of separate services implement this independently, and source extra black-listed values from each other, in any way that each one finds acceptable.

You end up with a distributed database, kindof block-chain style, but where it may be perfectly acceptable that some databases never end up agreeing with each other.

Part 2:
The "blacklist" part would necessitate checking if a new password is pre-existing. And this would always require a server-side check -- because no client-side system is going to download a terabyte of passwords just to create a new account, that'd be ka-razy. So you need a secure way to check if your new password exists in this Universal Password Blacklist. Luckily, we know how to do that.
1) Adopt a "cryptographic hash" that everyone is happy with
2) Run you tentative new password through this hash
3) Call new-service, to check if this hashed-value exists in their database of passwords
If it exists, it's a no-go, you need to input a better password. If it doesn't exist, go ahead and create the account.

Bonus: No Part 1 Necessary!
Alert security-wonks may have realized that this "read" can also function as a "write", So in Part 2, the "read" to check if that hashed value exists, can be implemented as a "write", like a SQL "insert". If the value already exists, you get a "duplicate key error", and you return to the caller that they should not allow the password that translated into this hashed value. If it did not exist, you successfully write it to the Universal Password Blacklist, and return to the client that this password has never been used before.

Side Note:
This hash should be used only for this system. That is, if someone else used exactly the same process for their internal password-storage, then everyone could "brute-force" attack the hashed values, and reverse it to find original passwords. Ideally this is still very difficult, but there is no reason to not add your own salt-and-pepper-hash to your system, that is distinct from the one adopted for the Universal Password Blacklist, and then this problem is non-existent.

Extra Risk & Mitigation
Risk:  The only real reason I can think of to not accept random inputs from all-comers is if you're worried about DOS-style flooding. Imagine someone hates security, so they just flood you with random "passwords", and you have to just soak up all this data and store it permanently, which is a burden that gives no benefit.

Migitation: I think you could force a client to solve a "hard" problem (burn a number of CPU cycles) per use. This should give a sufficient disincentive to fill your system with noise.

Thoughts?
I've run this by a few security-conscious friends of mine, and have found nothing to dissuade me that this is a great idea. I would love it if someone could find a problem with it. Or if someone, somewhere, would implement it. Either one would be great. I claim no patent, or any other ridiculous IP-right, on this idea, so please take it and use it!

Wednesday, January 11, 2017

In Defense of Passphrases


Ever since the XKCD comic on Password Strength became popular, I've heard more and more disparaging remarks about how passphrases are worse than more "random" passwords. I don't understand all the hating on passphrases; the basic idea of them, as I see it, is that words are easier for humans to memorize, and create associations between, than random gibberish characters.

Now it's a given that both "gibberish" passwords and long passphrases can both be done poorly -- "correct horse battery staple" is now a terrible password, because it was featured in the comic. But so is "2143658701badcfe" (even if you somehow think that string was random, the fact that it now appears in this blog post makes it a bad choice). But I think these naysayers do not understand the value that passphrases adds -- it is easier (for most people) to remember words than random characters, of the same entropy (if you aren't familiar with "entropy", think "randomness"). But let's try to prove it with some simple examples.

First, how much entropy is enough? That's a complex question, but for our purposes let's just say 80 bits; this is based on this Q&A entry. Whether 80 bits is enough or not doesn't really matter -- if you want 160 bits, just double the lengths of all the values below.

What does 80 bits of entropy look like in English? The English language allegedly has around 1,000,000 words. Now we can't use them all; for one thing, very rare words are hard to remember. So let's pick from the most common 10,000 words. I'm using the 10,000 words at the top of this github page. I made that list by taking another list, and spending just a few minutes cleaning it. But I don't think anyone would object to 10,000 being a reasonable number of words for someone to know, however you come up with the list.

Now each word has a 1 in 10,000 chance of being selected. This doesn't go quite evenly into 80 bits, but 6 words works out to be 82% of the 80 bits. (7 words would be 820000% of 80 bits, so let's stick with 6 words)

I grabbed 6 random numbers from 1 to 10,000, and got:
  • 6225, 1738, 4836, 6378, 7361, 8406.
Looking up those words on my list 10,000 word list gives:
  • objections, shoulders, breathe, comrade, angrily, vs
That's what 80 bits of entropy looks like in English. So how does that compare to more "conventional" randomly generated passwords?
  • Hex: 63485AE5638C1EDCC61E
    • 20 hex digits is exactly 80 bits of entropy.
  • Decimal: 236663118018716201382515
    • 24 digits is ~80% of the entropy of 80 bits; close enough
  • Base64: kiydPJHQh4jL7
    • A lot of systems try to use all the number, upper and lower case letters, and sometimes other characters thrown in. This is pretty awful for humans, both because it takes longer to type in, and it's often hard to tell a 1 from I from l, 0 from O, etc. But including here for comparison. The math works out that 13 characters in base64 is 2^78; that is only 25% of 2^80, it's close enough.
  • Passphrase: objections shoulders breathe comrade angrily vs
So now, you be the judge. You have to memorize one of these 5 choices; if you succeed, you will live a long and safe life, if you fail, your identity will be stolen and you will be miserable. Which one do you choose?

Or maybe you're trying to be "practical"; which one is easier to type in? Well I just timed myself typing in each one, on my normal keyboard, and my times were: 10 seconds, 8 seconds, 5 seconds, 7 seconds. So the terrible base64 was the fastest, presumably only due to the very low character count; but typing the long passphrase was second in speed. But I certainly wouldn't choose the base64 option, especially if you consider what it's like to type that into a phone/tablet; all the numbers and capital letters require multiple taps, it's terrible. Whereas the whole words could be swiped-in, since they are all recognizable, common words. I'm too lazy to try timing myself on a phone right now, but I would speculate that I can swipe-typing 6 words much faster than I can enter any of the other three random sets of characters above.

Now a real scientific test would be to formalize this a bit more, and run real memory tests on humans. But I think I have proved my point.

Just for fun/reference, here's the javascript code I used to help with the above:

var base64 = function(n){return '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/'[n];}
var rnd = function(max){return Math.floor(Math.random() * max)};
var rndDigits = function(base, digits){var s = '';
 for (var i = 0; i < digits; i++)
  s += base64(rnd(base));
 return s;}
console.log('Binary: ' + rndDigits(2, 80));
console.log('Decimal: ' + rndDigits(10, 24));
console.log('Hex: ' + rndDigits(16, 20));
console.log('Base64: ' + rndDigits(64, 13));
console.log(rnd(10000) + ', ' + rnd(10000) + ', ' + rnd(10000) + ', ' + rnd(10000) + ', ' + rnd(10000) + ', ' + rnd(10000));
// for marking times, I just pressed enter before and after each combination
document.addEventListener('keydown', function (e) { if (13 == e.keyCode) { console.log(new Date()); } }, false);

Friday, December 9, 2016

letItSnow() // javascript

// NOTE: This script does not work in some browsers, due to lack of support for SVG effects/animation.

'use strict';
function letItSnow(options) { // <3 CC0 
    var SVG_NS = 'http://www.w3.org/2000/svg';

    var getSnowflakeArm = function (x, y, radius) {
        // TODO: this could be enhanced to make much more intersting snowflake shapes, with input parameters and/or randomized values
        // round all numbers to a max of 2 decimal places
        var x0 = Math.round(x * 100) / 100;
        var y0 = Math.round(y * 100) / 100;
        radius = Math.round(radius * 100) / 100;
        var radiusOffset1 = radius / 5;
        var xArm1 = radius / 5;
        var yArm1 = radius / 10;
        var radiusOffset2 = radiusOffset1 * 2;
        var xArm2 = xArm1 * 2;
        var yArm2 = yArm1 * 2;

        var armPoints = [];
        // draw from center to tip
        armPoints.push({ a: 'M', x: x0, y: y0 });
        armPoints.push({ a: 'L', x: x0, y: y0 - radius });

        // draw first spoke -- from tip, to center, to tip
        armPoints.push({ a: 'M', x: x0 + xArm1, y: y0 - radius + yArm1 });
        armPoints.push({ a: 'L', x: x0, y: y0 - radius + radiusOffset1 });
        armPoints.push({ a: 'L', x: x0 - xArm1, y: y0 - radius + yArm1 });

        // draw second spoke
        armPoints.push({ a: 'M', x: x0 + xArm2, y: y0 - radius + yArm2 });
        armPoints.push({ a: 'L', x: x0, y: y0 - radius + radiusOffset2 });
        armPoints.push({ a: 'L', x: x0 - xArm2, y: y0 - radius + yArm2 });

        var d = '';
        for (var j = 0; j < armPoints.length; j++) {
            var p = armPoints[j];
            d += '\n' + p.a + p.x + ',' + p.y;
        }
        return d;
    }

    var getSnowflake = function (x, y, radius, speed, spin, clockwise) {
        var secondsSpin = (11 - spin) / 4; // spin should be from 1 to 10
        var sign = clockwise ? 1 : -1;

        var g = document.createElementNS(SVG_NS, 'g');

        // the bigger the snowflake, the thicker you need the lines to be
        var strokeWidth = Math.round(radius / 15 * 100) / 100;
        var d = getSnowflakeArm(x, 0, radius);
        for (var i = 0; i < 6; i++) {
            var path = document.createElementNS(SVG_NS, 'path');
            //path.setAttribute('id','Mine' + i);
            path.setAttribute('d', d);
            path.setAttribute('fill', 'none');
            path.setAttribute('stroke-width', strokeWidth);
            path.setAttribute('stroke', '#ffffff');

            var animate = document.createElementNS(SVG_NS, 'animateTransform');
            animate.setAttribute('attributeName', 'transform');
            animate.setAttribute('type', 'rotate');
            animate.setAttribute('from', (i + 0) * 60 * sign + ' ' + x + ' ' + 0);
            animate.setAttribute('to', (i + 1) * 60 * sign + ' ' + x + ' ' + 0);
            animate.setAttribute('dur', secondsSpin + 's');
            animate.setAttribute('repeatCount', 'indefinite');
            path.appendChild(animate);

            g.appendChild(path);
        }

        var h = window.innerHeight;
        var secondsFall = h / speed;
        var fallenBeginSeconds = secondsFall * y / h;
        var animate = document.createElementNS(SVG_NS, 'animateTransform');
        animate.setAttribute('attributeName', 'transform');
        animate.setAttribute('type', 'translate');
        animate.setAttribute('from', '0 ' + (0 - radius));
        animate.setAttribute('to', '0 ' + (h + radius));
        animate.setAttribute('dur', secondsFall + 's');
        animate.setAttribute('begin', -fallenBeginSeconds + 's');
        animate.setAttribute('repeatCount', 'indefinite');
        g.appendChild(animate);

        return g;
    }

    { // validation/defaults for all used option values
        if (typeof options == 'undefined') options = {};
        var validOrDefault = function (value, min, max, defaultValue) {
            if (isNaN(value))
                return defaultValue;
            value = Number(value);
            if (value < min || value > max)
                return defaultValue;
            return value;
        }
        options.numFlakes = validOrDefault(options.numFlakes, 1, 500, 75);
        options.minSize = validOrDefault(options.minSize, 5, 50, 5);
        options.maxSize = validOrDefault(options.maxSize, 5, 50, 20);
        options.minSpin = validOrDefault(options.minSpin, 0, 10, 3);
        options.maxSpin = validOrDefault(options.maxSpin, 0, 10, 7);
        options.minSpeed = validOrDefault(options.minSpeed, 0, 100, 10);
        options.maxSpeed = validOrDefault(options.maxSpeed, 0, 100, 90);
        if (isNaN(options.zIndex)) options.zIndex = -1;
    }

    // create <svg>, set to size of page, add animated snowflakes
    var svg = document.createElementNS(SVG_NS, 'svg');
    svg.setAttribute('width', window.innerWidth);
    svg.setAttribute('height', window.innerHeight);
    svg.style.position = 'fixed';
    svg.style.left = 0;
    svg.style.top = 0;
    svg.style.zIndex = options.zIndex;

    for (var i = 0; i < options.numFlakes; i++) {
        var x = Math.random() * window.innerWidth;
        var y = Math.random() * window.innerHeight;
        var radius = Math.random() * (options.maxSize - options.minSize) + options.minSize;
        var spin = Math.random() * (options.maxSpin - options.minSpin) + options.minSpin;
        var clockwise = (Math.random() > 0.5);
        var speed = Math.random() * (options.maxSpeed - options.minSpeed) + options.minSpeed;
        var snowflake = getSnowflake(x, y, radius, speed, spin, clockwise);
        svg.appendChild(snowflake);
    }
    if (isNaN(options.delay) == false) {
        var delaySeconds = Number(options.delay);
        if (delaySeconds > 0) {
            svg.style.display = 'none';
            window.setTimeout(function () { svg.style.display = ''; },
             delaySeconds * 1000);
        }
    }
    document.body.appendChild(svg);
}