Friday, June 19, 2020

COVID-19, Masks, Idiots, and You!

In this post, we'll go through what COVID-19 is, the data around the pandemic, how to interpret the data, and what to do to keep everyone safe.

The US government is turning COVID-19 safety into a political issue. This is stupid since then virus doesn't care who you vote for and will try to infect everyone equally. Spreading misinformation and turning public health safety efforts into political lines in the sand will only help the virus and delay the healing. I'd like to end the pandemic sooner, so am writing this post to collect thoughts and resources to help. I have a Masters in Public Health studying respiratory virus transmission. DARPA paid for half my tuition so my lab could figure out how to predict if soldiers would get the flu before missions when they were still asymptomatic (no coughing, sneezing, fever, etc.). Bruceman paid for the other half. I know enough to call bullshit on a lot of the misinformation floating around, and can point you towards good trustworthy sources. Hopefully this helps!

What is COVID-19?

COVID-19 is an acronym standing for coronavirus disease of 2019. COVID-19 is a respiratory virus spread from person to person. Pretty much a sick person will be breathing out COVID-19 particles and sneezing/coughing up snot droplets everywhere around them. If a non-sick person breathes in these particles or droplets they get sick. If they touch one of those snot droplets then touch their eyes or mouth, it's just like breathing in the virus, and they'll get sick.

Once someone's been exposed to a sick person and potentially has the virus inside their body, COVID-19 needs 2-14 days to grow and multiply inside the new host person to the point where symptoms develop. While the virus is still growing, people are considered asymptomatic (they don't have symptoms). This is the dangerous time because they feel fine, but can still spread the virus to others. Infected people are most contagious when they have symptoms, but will still spread the virus to others before they feel sick. This makes controlling and stopping the spread of COVID-19 very tricky.

"COVID is just the flu"

COVID-19 is dramatically different from influenza viruses (the flu). They vary at the phylum level of taxonomy. This is like comparing a human to a mushroom. Check out how Pokemon vary at the phylum level if you want to see how ridiculous this is. Dramatically different biology at work, even if they present with similar symptoms.

Main differences include the following:
  1. COVID-19 is easier to spread than the flu.
  2. COVID-19 results in more severe complications than the flu.
  3. COVID-19 mortality is higher than the flu (>30x according to the World Health Organization).
Good sources to read:

The Data - How bad is it?

It's not looking good in the United States. Cases are on the rise after prematurely ending quarantines. Google does a pretty good job of showing new cases and will tailor the results based on where you're currently browsing from. For more detail, each US state setup their own variation of tracking. Here's the site from my home state of Michigan which has one of the lowest COVID-19 levels in the USA.  Our World In Data is doing a fantastic job compiling and visualizing the world's COVID data. Their interactive tools let you see many different angles. Max Roser's twitter is fantastic for commentary on the analysis and tools if you'd like a guide to understand what we're looking at.

How to interpret the data?

In the United States, the Republican party leaders are ignoring science and turning the pandemic into a political issue. This is one of the least effective ways to fight a virus and is causing a whole cascade of policy screw ups that have caused 118,434 deaths and counting in the United States (as of June 19th, 2020).
You can avoid political bias by looking at how other countries are reporting on and treating the United States. Living in Michigan, I pay attention to how Canada treats us.
This is a good summary of what happened in the United States. We reopened the country prematurely.

To stay informed, I look at the daily numbers of cases and deaths in Google and Our World in Data. I pay close attention to Andy Slavitt and the CDC Director for added context to the story. Andy gives daily detailed status updates simplifying what experts are saying. Highly recommend giving him a follow. Here's his update from June 19th (click into twitter to see the full thread):

How to stay safe?

The United States President is currently a public health idiot. DO NOT take advice from Donald Trump about staying safe from the coronavirus. We're fighting a pandemic, not selling a used car. His false confidence will get your friends and family killed.
The best way to stay safe is to wear masks and social distance. Wash your hands after any contact with anything someone could have sneezed on. If 80% of us wore face masks, the virus would stop spreading and we could end lock downs and quarantines sooner. Here's a few diagrams showing how masks protect others.
Most people won't get severe symptoms when they get sick. Mask wearing is to protect older people and people with weaker immune systems where getting sick is a death sentence. If you're 50 years or older, you have at least a 5% chance of dying if you become infected with COVID-19. This increases dramatically as age goes up.Wearing a mask is the easiest thing you can do to stop the virus from reaching these populations and save a life today.

Tuesday, February 6, 2018

100 Thieves vs TSM post game analysis - Week 3 NA LCS

Want to get better at strategy analysis so going to try to do one of these a week for a while. Figured I'd start with a 100 Thieves game since we just bought them. Also Hauntzer vs Ssumday is a powerhouse top lane matchup!

Here's the game VOD if you'd like to follow along at home:

Post game stats:

The Draft:

First Bans - Nothing too crazy. 100 Thieves targets Bjergsen's pool with Azir/Galio, and takes away Hauntzer's Camille. TSM likes these champs that can hold people in place for key abilities. Think Camille ultimate into Gallio ultimate with Azir Shurima-shuffling champs into that wombo.

TSM takes away Ryu's Ryze (he's been playing really well on it), Zoe's just annoying to play against with the long range cc + poke. Not sure what the Corki ban is from? Haven't seen Ryu or Cody Sun play that champ yet. Might be showing that TSM wants something like a Kassadin and doesn't want to get bullied in lane by a ranged champ early?

First Picks - 100T locks in Kog'maw for ADC first pick. High priority pick since this champ is arguably the best ADC right now, and does WAY too much damage with 2+ items. Kog'maw means 100T want a long game and lots of farming early.

TSM counters with Sejuani (probably jungle) and Taliyah (probably mid). Champs with pick and burst potential to isolate and quickly kill a fed Kog'maw. This leaves champs like Lulu or Karma up which Kog'Maw loves for the shields/sustain/crowd-control and extra stats from their support itemization.

Second Picks - 100T locks in Jarvan IV in the jungle and Alistar in the support role. They have a good frontline for Kog'Maw that can make picks or and control the area around Kog'Maw. Alistar pick is a melee champ and they may be trying to limit the melee support pool of TSM since you usually want 2-3 melee champs to stack her Permafrost stun. No shields for Kog'Maw, which is interesting, but maybe they run something like Lulu mid/top?

TSM locks in Gangplank (probably top). Melee champ for Sejuani, and another damage threat. Both Kog'Maw and Gangplank have late game power spikes after a lot of farm, so TSM probably trying to equal damage curves. If Jarvan IV ults a target, Gangplank can drop his ultimate on the same area to stop 100T from following up. This will also pull Meteos's J4 top early/mid game to stop Gangplank from farming, leaving Kog'Maw susceptible to early dives bot.

Second Bans - TSM still has to pick their botlane, so 100T uses their bans to target those picks. Braum is one of the best supports to play with Sejuani (melee + extra crowd control), and Tristana can jump out of Jarvan IV ult with her E, and can make picks with her Ult potentially repositioning Kog'Maw into unfavorable territory. Smart bans.

100T is yet to pick their solo lanes. Looks like TSM target bans Ssumday's ranged top laners so that he'll have a hard time bullying Gangplank in lane and stopping him from farming. The Gnar ban is especially smart since it denies 100T the Gnarvan combo (J4 ultimate into Gnar ultimate AOE stun).

Third Picks - TSM locks in Ornn for support. Melee champ that synergizes well with Sejuani's stun passive, and has abilities that elongate crowd control durations. Also, Braum is banned, which is usually a good Ornn counter (Ornn ults, Braum uses his shield to block/cancel the initial Ornn Ram). Granted 100T has already locked Alistar, but still!

100T locks in Twisted Fate (probably mid) and Vladmir (probably top). They are heavy on the magic damage (Kog'Maw does a lot of magic damage for an ADC), and don't have a good front line for keeping Kog'Maw safe. Aphromoo's Alistar is going to have to put in some WORK to keep his carry safe. 100T is probably opting for a 1 3 1 type comp, with Vladimir and Twisted Fate split pushing. Vlad can join fights with Teleport, TF can use his ultimate to join fights or make picks off of bad rotations. Vladimir can sustain through Gangplank's harass and can use his pool to dodge a lot of scary abilities (Sejuani/Ornn stuns). Vladmir's ultimate will also amp Kog'Maw's damage in team fights if they can hit the same targets.

Final Pick - TSM locks Varus for ADC. Their team comp is looking to initiate with Orrn Ultimate, into Varus ultimate, into Sejuani crowd control, with a Gangplank ultimate and Taliyah damage on top of everything. They have pick potential with all champs (Gangplank can always just press R to help) and can deal with split pushes or a 5v5. I really like TSM's comp and think they're better off.

100T is looking to 1 3 1 and catch TSM in bad rotations to take objectives. Once Kog'Maw gets a few items, they may look to team fight, but TSM's 5v5 is better and should be able to zone Kog'Maw out from doing damage. 100T is looking to get strong early (probably through midlane) and roam to take early towers with TF, opening up the map for their split push. This doesn't give Kog'Maw time to farm safely, but TSM's team comp is just better, so they're looking to end quickly.

Early Game:

Start - Things to notice! TSM taking teleport and cleanse on their solo lanes vs 100T 2x ignites. 100T wants to win early! Botlane both supports have ignite. This is going to be an aggressive early game! Both teams fan out and guard their respective jungle entrances. No cheesy 5man invades, or risks for deep vision. Laning is where the aggression will come out. TSM knows they win if the game stalls out, so the impetus is on 100T to make something happen.

Jungle Starts - Both 100T and TSM start their bottom buff. Notice that they're not doing wolves or raptors for the extra experience (kill big monster, leave littles, clear after another big monster is cleared for 50 extra exp). This means both junglers are looking to hit level 3 fast, and looking to control top/mid. J4 is much stronger early game compared to Sejuani, and can do cheesy things like ganking level 2 with red buff mid/bot lane for some unexpected aggression. Sejuani is looking to farm and get level 6 (when she becomes stronger than J4), and match or counter gank until then.

3:25 - Not a fan of Meteos's clear pattern. He doesn't use his powerspike at level 2, or 3. And is behind Sejuani by a camp and lacking control (look at the scuttles and wards on the bottom half of the jungle). Gangplank's lane is ungankable and he is able to farm (what he wants to do), and botlane is perfect for Kog'Maw farming. Looks like Meteos is content to powerfarm jungle and wait for TF to hit 6 for ganks? One thing that's nice is that TSM doesn't have a good idea where Meteos is on the map due to his weird clear and not being spotted out by wards. But again, this strategy isn't the greatest since TSM's comp will outscale 100T, so doesn't fit the "win early" strategy.

No aggression yet, TSM with slight creep score advantages in every lane + jungle.

3:53 - AYYYYYY I was wrong! Very clean play by Aphromoo to setup first blood and blow Varus Heal. Surprised that worked, but TSM did not respect the engage. Alistar can cancel his knock up animation with flash, so you pretty much need to predict that engage to dodge on champs with no crowd control or dodges to interrupt. Notice that Kog'Maw used his heal offensively to speed up both Alistar and himself. Think this was overkill and unnecessary, but still, nice kill, good gank, and gold onto Kog'Maw plus the extra waves of creeps to farm!

5:43 - Twisted Fate hits 6. This is when 100T's team comp should start making plays across the map. Expect Ryu to shove waves and roam to side lanes with his ults. At 6mins, the stopwatch rune activates and everyone but Sejuani gets a free Zhonya's active. So dives will be tricky. That rune can be used offensively to juggle tower aggro, or defensively to deny dives or that last tick of ignite. Notice the double control wards on the bottom side of the map. They know TF wants to roam bot and are putting those there to spot him out!

TSM is ahead in gold for all lanes except botlane. So time for 100T to start making plays before they get outscaled.

9:23 - Nothing happens for a while. No ganks, no roams with TF ultimate, just farming. TSM makes a good play on the Ocean dragon when the 100T botlane backs. Mike Yeung blows Kog'Maw flash with Sejuani ultimate, but then gets picked off by an Alistar/J4/TF wombo when overextended trying to clear a ward. Nothing in the area objectivewise for 100T to capitalize on. 100T are up a slight amount of gold from the early kills bot. Farming continues, which again, is what TSM wants since their team composition is better. Interesting that TSM did not go for ganks mid against TF. Usually champs with no dash or escape are an easy burst combo for Taliyah. Maybe a missed opportunity to gank the global?

13:57 - Good wards on the TSM blue jungle reveal Sejuani backing, which lets J4 pick up a free Rift Herald. He's seen from a river ward, but TF uses ultimate to scare off enemy team, and Ssumday teleports in (spellbook rune changed from ignite) for extra muscle to scare off Sejuani's steal attempt.

15:25 - Four man roam bot scares away Alistar/Kog'Maw, allowing TSM to pick up the first cement bonus from killing the first tower of the game. Meteos sees this happening and summons the rift herald top to counter with a tower and a half worth of damage. Hauntzer's Gangplank backs off respecting Vlad+J4's kill potential. TSM has 100T's red jungle lit up with wards, and is likely looking to control the 2nd Ocean dragon after everyone recalls to spend their gold.

16:47 - TSM's vision control pays off and they're reward with an uncontested Ocean dragon (their second of the game). All TSM carries are happy they can spam abilities on cooldown. A Mountain Dragon is spawning next in 7mins. Both teams will likely prioritize this objective.

17:47 - Ryu's TF makes a nice pick on Zven's Varus bot, blowing both Varus summoner spells. Ssumday makes a rare mechanical error flashing after Varus (Didn't lead with his ult), and dies to the remaining members of TSM collapsing around him. This is a mistake from 100T who need to take the blown summoners and back off instead of forcing things (but the playmaking is understandable since they're up against the clock to end the game early). Cody Sum's Kog'Maw overextends toplane and is picked off by the Hantzer's Gangplank, blowing Kog'Maw stopwatch and heal. Both teams making some positional mistakes.

20:00 - Nothing fancy happens. Remaining side lane outer towers are traded. J4 burns Gangplank's flash with his ultimate. Gold is in TSM's favor by ~1k plus the 2x Ocean Dragons. TSM is itemizing heavily into magic resist items, and is in a good place, scaling into their superior team comp mid-late game.

100T begins their 1 3 1 with Vladmir and TF in the sidelanes, matched by Taliyah and Gangplank respectively. Really like those TSM picks since Taliyah can use her ultimate to follow TF, and Gangplank can click R to help if his teammates get engaged on. Hauntzer probably could have itemized into an executioner's calling (800g item) for the grevious wounds passive to negate Vladmir's sustain. Instead he opted to finish core items and farm waves back and forth with Ssumday (who just heals off any chip damage).

Both teams have vision around Baron Nashor. TSM will likely wait until the Earth Dragon spawns, and force 100T to choose between objectives. Both teams have global ultimates, teleports, and can move across the map very quickly.

24:07 - TSM controls the dragon pit, and secures the Earth Dragon as 100T watches helplessly. Next Dragon will be a 3rd Ocean.

TSM's team composition is starting to look far superior and 100T can't engage 5v5 without suffering heavy casualties. Hard for 100T to split since TSM's champs can easily match the pressure, and still contribute in team fights with global ultimates. Next objective is likely Baron or mid outer tower for TSM who is in complete control. 100T is looking for TSM to make a positional mistake, and pick someone off, or get an AMAZING team fight where Kog'Maw survives long enough to kill everyone on TSM. Which will be tough with their team comp.

26:17 - Here's an example of the team comp difference. TSM executes this very well. 100T cannot win a sustained all in, and must rely on picking off members of TSM that aren't grouped. 100T is now down a teleport, which TSM can abuse to gain control over the Baron pit.

28:00 - TSM patiently and methodically pushes down the 100T mid outer turret. Here the 2x Ocean Dragons are starting to really hurt 100T. TSM can continue trading, and then regain health and mana after a few seconds, while 100T are slowly getting whittle down. Sejuani continues to fish for Kog'Maw with her ult, looking to blow summoner spells or his QSS. TSM is building this pressure while their top wave crashes into the 100T inner tower. A 100T botlane wave is slow pushing which will need to be cleared by TSM in a minute or two (worst case Gangplank can always use his ultimate on it). The Baron dance is about to begin, with TSM in a superior position.

30:18 - 100T blows Varus flash, and then looks to bait a fight at Baron. They are down 3.7k gold, 4 dragons, and their team composition is far inferior to TSM. They're looking to get Baron and force a powerplay before TSM completely outscales them. Remember, TSM has Ornn items as well, so they WILL be stronger if the game goes full builds.

Meteos throws the game at ~31mins. Not sure what he was looking to do with the engage minus follow up from Kog'Maw and Ssumday putting pressure bot. Wasn't looking good for 100T anyway, but this did not help.

34:22 - TSM is in complete control and takes two towers, an inhibitor, and the two nexus towers after Meteos gets caught out of position. Ryu takes the TSM outer mid tower as a parting gift, and Kog'Maw is able to get a few kills, but now it is only a matter of time before 100T loses. TSM can wait for super minions to build pressure bottom, and control the Elder Dragon with their 4-0 Dragon lead, then end the game with one final push.

35:52 - TSM sets up for the Elder Dragon. Notice their top wave. It is setup to slow push and build pressure exactly when the Elder Dragon spawns. That pressure, plus the super minions pushing in the bottom lane, should allow TSM to ward up the 100T Red jungle, and control the Elder Dragon. Very well played by TSM.

38:29 - TSM controls the Elder Dragon and heads to Baron. Good patience by TSM to wait for an engage until the big wave top was taking an outer turret. This spread 100T too thin, and allowed for a TSM pick and control of the Elder Dragon. Now an easy rotation to Baron, and end the game from the bot lane for TSM.

41:05 - GGWP TSM wins.

Post Mortem:

What could 100 Thieves done better?

1. They got severely outdrafted. This is the main reason 100T lost. Kog'Maw is a huge powerpick, but needs a frontline or shielding supports to allow him to do the damage. 100T had J4 and Alistar to frontline, but TSM had too much crowd control and zoning abilities for Kog'Maw to get close enough to do damage before his frontline melted. TSM had answers to the 100T 1 3 1 split push comp, and a better 5v5 teamfight. Plus, 100T was mostly magic damage with a tank Jarvan IV out of the jungle. Made it easy for TSM to itemize magic resist items to make team fights and picks tougher to pull off. Ugly draft for 100T, very well played by TSM!

2. 100 Thieves needed to make more happen early to force a win before their team comp got outscaled. The early Alistar -> J4 gank bot was beautiful! They needed to do that more often and punish TSM every time a flash was down. Very surprised Meteos didn't camp the Taliyah mid since J4+TF have more damage than Taliyah + Sejuani. TF gold card is a free J4 E-Q combo, with his ultimate following the Taliyah flash. Maybe scared because of her cleanse? But you can't cleanse the J4 ultimate...

Ssumday could have played something with more damage top to put more pressure on the Gangplank, but TSM banned his two practiced damage dealers. Vladimir auto pushes the lane with his area of effect abilities, so easy for Gangplank to farm safely under tower making it tough for Meteos to make plays.

3. Meteos needs to stop doing bad engages late game. This is a bad habit that repeats itself in most 100T games. Meteos will engage without his team, or without waiting for waves to create enough pressure. He's been lucky a few times where his team has still gotten the W, but it completely threw the game to TSM in this game.

4. Better Dragon Control. Giving up the early dragons for free, made TSM's carries and siege a nightmare to deal with later. It also made Elder Dragon an extremely important objective since TSM was able to stack 4 Dragons early.

5. Better wave management. 100T could have used minion wave mechanics to create pressure and force TSM to split up, giving them an advantage. Granted Gangplank or Taliyah can both use their ultimates to get back into the team fight when they go to fix the waves. Again, the draft was brutal for 100T, if they fix that, a lot of these problems go away.

This was a fun game to watch and I've enjoyed watching 100T get better. Fun watching a new team hold their own against these legacy power houses. Still very early in the season and a lot of time to improve and fix mistakes!

Let me know if this is interesting on Twitter or Twitch! Will try to do more of these if people are into them. Thanks for reading!

Friday, June 30, 2017

Code For Twitch Chat Bot

Stole Modified some fantastic code I found on the internet to build a python bot to deal with some unsavory characters on my twitch channel. Some scumbag was making rape threats against my family members, so me or my mods would ban them. Problem was, they would just fire up new twitch accounts and continue the barrage. So, being lazy, I built a bot that can be turned on/off, that would look for brand new accounts, and automatically time them out of chat for exactly one day. That way, they would still watch my channel and boost my twitch numbers, but they would do so in glorious silence.

Also my bot is called DavidSPumpkinsBot and will type "Any questions???" as it does the things.
(It's the little stuff, right?)

Posting the Python code here (minus my authentication codes, for obvious reasons), so my friend Das Ranger can build his own! Enjoy!

Python code:

#! TwitchBotBusterCode
# -*- coding: utf-8 -*-

from __future__ import print_function
import socket
import time
import re
import requests
import json

# The twitch user which will be acting as your bot
chat_user = 'davidspumpkinsbot'

# The oauth password for the twitch user which will be operating
# Obtain this value by visiting this page while logged in as your bot user:

# The channel the bot will be operating in (your channel's name)
chat_chan = 'gundaymonday'

# Punishment method; change to timeout (or any word other than ban) to make it timeout instead of ban
punishment = 'timeout'

# If using timeout punishment, duration of the timeout
timeout_duration = 3600

# Twitch IRC server info: the host and port should not need to be changed
chat_host = ""
chat_port = 6667

# List of commands which use regular expressions. Only change the left side and make sure to leave the ^
commands = {
    '^!startbans': 'start_banning',
    '^!blacklist': 'blacklist_user',
    '^!bld': 'blacklist_date',
    '^!unlist' : 'unlist_date',
    '^!whitelist': 'whitelist_user',
    '^!stopbans': 'stop_banning',

# Initial IRC socket connection and authentication
s = socket.socket()
s.connect((chat_host, chat_port))
s.send("PASS {}\r\n".format(chat_pass).encode("utf-8"))
s.send("NICK {}\r\n".format(chat_user).encode("utf-8"))
s.send("CAP REQ\r\n".encode("utf-8"))
s.send("JOIN #{}\r\n".format(chat_chan).encode("utf-8"))

mitigation_active = 0

# Function for threaded asynchronous functions decorator @async
def async(func):
    from functools import wraps
    def async_func(*args, **kwargs):
        from threading import Thread
        f = Thread(target = func, args = args, kwargs = kwargs)
    return async_func

# Sends messages to chat
def chat(sock, msg):
    sock.send(bytes('PRIVMSG #%s :%s\r\n' % (chat_chan, msg), 'UTF-8'))

# Permabans users
def ban(sock, user):
    chat(sock, "/ban {}".format(user))

# Unbans users
def unban(sock, user):
    chat(sock, "/unban {}".format(user))

# Times users out
def timeout(sock, user, secs):
    chat(sock, "/timeout {} {}".format(user, secs))

# Punishes chatter with specified method
def punish(chatter):
    if punishment == 'ban':
        ban(s, chatter)
        print('Banning {}'.format(chatter))
        timeout(s, chatter, timeout_duration)
        print('Timing Out {}'.format(chatter))

# Gets list of chatters and updates admin list
def get_chatters():
    # Loop ends when a value is returned
    global admin_list
    while 1:
        chatter_list = ()
        admins = ()
        # Uses try because sometimes the connection times out
            r = requests.get('{}/chatters'.format(chat_chan))
        if r.status_code == 200:
            r = json.loads(r.text)
            # Builds the list of chatters
            for chatter in r['chatters']['viewers']:
                chatter_list += (chatter,)
            # Dynamic moderators list, kinda hackish but if you unmod someone they will be removed on the next loop
            for moderator in r['chatters']['moderators']:
                admins += (moderator,)
            admin_list = admins
            return chatter_list

# Function for returning the user's creation date
def creation_date(user):
    # Loop ends when a value is returned
    while 1:
        # Uses try in case of request timeout
            r = requests.get('{}'.format(user))

        if r.status_code == 200:
            # Captures only YYYY-MM-DD
            date = re.match(

# Thread for watching and banning chatters
def watch_chatters():
    global chatter_list
    while 1:
        # Gets the list of chatters in the room, needs to run while mitigation is off
        chatter_list = get_chatters()
        if mitigation_active:
            for chatter in chatter_list:
                if chatter in banned_users:
                if chatter in whitelisted_users:
                if creation_date(chatter) in banned_dates:
                    if punishment == 'ban':
                        ban(s, chatter)
                        timeout(s, chatter, 3600)

                    print('Current number of banned users: {!s}'.format(len(banned_users)))

def process_chat(chat_object):
    user =
    msg =
    # Initialize variables
    global banned_users,whitelisted_users,mitigation_active,banned_dates
    banned_users = []
    whitelisted_users = []

    banned_dates = []

    #Processes possible commands from the command dictionary
    for command,action in commands.items():
        if, msg):
            #manual blacklisting of dates, uses GMT always
            if action == 'blacklist_date' and user in admin_list:
                input ='^'+command+'\s+([\d]{4}-[\d]{2}-[\d]{2})', msg)
                if input:
                    #adds date to the banned list
                    chat(s, 'Error: invalid format. Use !bld YYYY-MM-DD')
            # Manual unlisting of a date, can also !stopbans to clear the list
            elif action == 'unlist_date' and user in admin_list:
                input ='^'+command+'\s+([\d]{4}-[\d]{2}-[\d]{2})', msg)
                if input:
                    chat(s, 'Error: invalid format. !unlist YYYY-MM-DD')

            #Automatic lookup of a user's creation date blacklisting it.
            elif action == 'blacklist_user' and user in admin_list:
                target ='^'+command+'\s*@?([\w\-]+)', msg)
                if target:
                    #Makes sure the user is actually in the room to prevent erroneous bans.
                    if in chatter_list:
                        user_date = creation_date(
                        chat(s, 'Blacklisted: {}'.format(user_date))
                        chat(s, 'User {} not found in the room. Check spelling or try again in 15 seconds.'.format(

            #Stops bans and clears blacklist
            elif action == 'stop_banning' and user in admin_list:
                mitigation_active = 0
                banned_dates = []
                chat(s, 'Turning off chat mitigation.')

            #Starts banning users
            elif action == 'start_banning' and user in admin_list:
                mitigation_active = 1
                chat(s, 'Turning on chat mitigation ═══█❚')

            #unbans and prevents future banning of the user
            elif action == 'whitelist_user' and user in admin_list:
                target ='^'+command+'\s*@?([\w\-]+)', msg)
                if target:
                    chat(s, 'Whitelisting user: {}'.format(
# Thread for reading chat and watching for user commands
def read_chat():  
    # Starts infinite loop listening to the IRC server
    while True:
        response = s.recv(1024).decode("utf-8")
        #PONG replies to keep the connection alive
        if response == "PING\r\n":

        #separates user and message.
        chat_object ='^:(\w+)![^:]+:(.*)$', response)
        if chat_object:
if __name__ == '__main__':
    chat(s, 'Any questions???')

Thursday, November 12, 2015

Nukulibrium Art Assets

Concept art for a potential game I'm working on. Sketches by Jody Osentoski, tech tree by Joe Hopkins.

I'm imagining the intro/hype video to have the Death God throw a necrotic looking spear, only to have the Life Goddess wave her hand and turn it into a spray of flowers and a dove. The dove then flies up only to be struck by lightening and turned into a drumstick, which the God of Fried Chicken then catches and chows down on. Zoom out to game title, tap anywhere to begin playing.

Waximus, the God of Death - Rough Character Sketch

Waynia, the Goddes of Life - Rough Character Sketch

Drumstick, the God of Fried Chicken - Rough Character Sketch

Another idea for Drumstick.

Example objective tree of things the gods would need to help humanity build to colonize Mars.

Tuesday, May 19, 2015

Bad Experiences by Design

Bad design, well, sucks.

We live and play in a designed world. The screen you're reading, the seat you're sitting in, the electricity you're consuming is all there, for better or worse, by someone's design.

I can count the number of times these have been used on one hand.

Which is why it's important to study design, both the good and the bad, to understand how to build better systems. Thinking through systemic design is important. Without foresight, you can leave your users frustrated and extremely vocal about the system's shortcomings.

So how do you avoid a design problem like this? Well, a simple trick is to put yourself into the shoes of a future user. If they knew nothing about the design, would they understand what to do at a glance?


Another tactic is to test drive your design before it hits the big stage. Jesse Schell recommends using kindergartners. If they get your design, then everyone else will probably understand too. They're also pretty brutal with their feedback and hold nothing back. You'll recognize a problem very quickly when a 5 year old is tearing your design a new one.

Any door with an instruction manual is a design failure.

Design is like a joke, it's not very good if you have to explain it. How many times have you pulled on a door with a "Push" sign above the pull handle? This design flaw is EVERYWHERE! There are books with entire chapters dedicated to badly designed door handles.

It's pretty clear you have to push this, hence no "PUSH" sign.

Riot Games recently made a frustrating design decision with their League Point (LP) system. When you win a match of League of Legends, you gain LP. When you lose a match, you lose LP. Win enough, and you are promoted to a new shiny tier with a new shiny badge. Lose enough, and the opposite is true.

Sounds great right? A clear indicator of how well you're doing and if you're getting better or worse at the game! Well, sometimes.

Riot decided to add in an extra tier just above diamond (where the top 1-2% of the playerbase plays) called Master Tier. When they did this, they made it so players newly entering the Diamond tier could not gain or lose LP in a regular fashion. Essentially, you would win a game, get +10 LP, lose a game, get -30 LP. So a win and a loss was one step forward 3 steps back. Check out what it did to the Diamond player distribution:

Click the picture to make it bigger. The player count in Diamond V is the interesting part.

Currently ~73% of Diamond Players are stuck in Diamond V. This is significantly higher than any other tier's V distribution (For example, it's only ~42% for the Gold Tier).

This has left a lot of the top players frustrated. The system that's supposed to tell you if you're improving or playing worse is automatically set to tell you that you do not belong (regardless of how you're playing). The reddit rage is pretty hilarious to read.

That frustration is translating into gameplay. The top 1-2% of the playerbase is now trapped in the most toxic part of the player distribution. You have a mix of the frustrated players desperately trying to get to Diamond mixed in with the chill players that got Diamond, but don't care anymore and are learning new champions. Add to this a dash of tryhard rage stuck in a one step forward, three steps back cycle, and you get a play experience where every match is pretty angry. That anger is sucking the fun out of the game.

A system designed to give feedback on performance, now has a choke point that does the opposite. This has created a ton of frustration which is translating into toxic gameplay (people raging at one another, intentionally throwing games, quitting the games early). Riot Lyte recently gave a talk at GDC showing that this kind of toxicity makes a player 320% more likely to stop playing a game.
They probably want to fix this since their most diehard players are getting hit with this systemic toxicity. Lyte also has the solution in his talk when he says that "Clear feedback is everything." This would all be fixed if the LP system did it's job, and told players how they were performing. Fix that, fix the toxicity, everyone's happy. Hopefully Riot realizes this soon and saves the day!

Don't be a victim of bad design.
So when you're designing the next big thing, take a step back and look at it with fresh eyes.

Is it easy to understand?
Is it working as intended?
Can a 5 year old get it?

A little foresight goes a long way.

Tuesday, January 20, 2015

Sejuani Mathcrafting for Patch 5.1 (League of Legends)

Tomorrow Season 5 of League of Legends begins. This means that the ranked ladders will reset and and everyone will need to climb back up to the top! Generally Riot will do something like:

(Your preseason MMR + 1200) / 2 = Your new Season 5 MMR

Worldwide League of Legends player distribution.
Compliments of

1200 MMR is the average league of legends player. So Riot will do a soft reset pulling everyone towards the average. This means that I get to be matched with and against lower MMR players, who will probably make more mistakes than what I'm used to. To compensate (and win games doing it), I did a little math to figure out a solid build/strategy for early game dominance.

(One of the top ADC players playing against the very bottom of the playerbase)

Against less skilled players, games are often decided in the first 20 mins of the game. If you can apply enough pressure or demoralize the enemy team, they'll often surrender at this point. My favorite champion is strongest late game (~30-40 mins), so I need to adapt my playstyle/build for the start of the new season. Here's my plan (math included) for doing this.

How to win the early game

Have you ever heard of a player named Ryan Choi? He is one of the best Rengar players in the world. The reason I mention Ryan, is that he made popular an interesting strategy to mitigate early game statistical weaknesses on his favorite champion. He stacks Doran's items (items that are cheap, give very good stats per gold spent, but don't build into stronger items) until he has enough gold to afford his more expensive late game build. Here's an example of how it works:

My favorite champion is Sejuani. She is a late game monster tank that becomes pretty damn unstoppable as the game goes on. But like I already mentioned, most games are decided in the first 20 mins, so I need to tweak my starting item build path (much like what Ryan Choi does) to compensate for my weak early game. Here's the item that's going to win me games tomorrow:

Here's why. Sejuani's damage scales with her maximum health, and ability power. It usually takes a while for her to buy enough items to be really scary though. Early game, she has trouble staying out on the map due to running low on mana (fuel for her abilities that do damage). Stacking Doran's rings gives her extra damage, and extra mana to compensate for her early game statistical weaknesses. Here's some math comparing what we could buy when we recall ~7 mins into the game ~level 5 with ~1600 gold (We should already have our upgraded jungle item).

What would you want to buy with ~1600g, to be strongest in the next 5 mins of the game?
The idea here, is to get our upgraded machete as quickly as possible since it gives us 30g per large monster kill and a bunch of extra damage/sustain. On our next buy, instead of buying REAL items, you grab 3-4 doran's rings. BECAUSE THEY GIVE US MORE STATS than real items at this point in the game!!! We will have more damage, and due to the mana regen and passive (free mana per unit kill), we should never run low on mana in the jungle. We'll also be using some % max health seals/quints to make the health worth a little more as well (we already talked about why this is smart in a past post). If we buy 4, we're spending 1600g. We can sell these back for 640g, so we're really only down 960g. If we can farm a few extra camps, get a kill or two, or win a tower/dragon after this buy, the Doran's rings pay for themselves.

(The power of Doran in a championship game)

As the game progresses, we'll sell off our Doran's rings to make room for better items. Hopefully we will have already made a bigger early game impact by the time that happens. The season resets tomorrow (1/21/2015), so feel free to tune into my stream on Twitch if you'd like to watch and see for yourself:

Watch live video from GundayMonday on

Thursday, January 15, 2015


New year, new goals.

Over the past year I focused a lot on my job at Quicken Loans. Would spend weekends building my coding chops (I am now a SQL deity) and learned how to do a lot of cool stuff. But I didn't get a chance to do a lot of the things I used to love. This year I want to fix that.

Got over my fear of public speaking in this band. Also my fear of everything.

Music has always been a big driving force in my life. Nothing is better than a great song. I admire those that create masterworks capable of shaping emotion from sound. My heroes are those that conjure the obscure, the triumphant, and the different kinds of tunes that make feet tap and smiles form.

(My favorite song)

I used to make music every day. Last year, maybe a month or two? And I only finished two songs.

This year, I want to change that. I want to create a full album that I can really be proud of. I want to take my time and really do it right. Spending an immense amount of time focusing on tweaking the little things to make it perfect. Don't even care if anyone else likes or listens, this one's for me.

So that's resolution #1, make an entire album of music that I can be proud of.

Next resolution's a little nerdier. I'm a pretty good League of Legends player. I'm the best Sejuani (Pig riding champion) in North America currently, and am in the top 10k players out of the entire continent (Just hit D3 ranking last night!). That's pretty good for a game with a multi-million playerbase. I want to do better though.

Riot recently created a new Master Tier to hold the absolute elite of the competitive player base. I want to break into it. I want to do it my way too. Most of the players up there use a specific playstyle that specializes in killing other players (they carry bad teammates this way). I want to break into the top tier by playing tanks and supports (champs that specialize in making teammates better). I'm already pretty close, and I think it's doable, but there aren't many tank/support players currently in the top 0.01% of the player base. I want to be one of the first.

Resolution #2 is to break into Master Tier for my favorite game while still having fun and playing it my way.

3rd and final resolution coming up (Wanted to keep it doable!).

Watch live video from GundayMonday on
Recently built my own computer from scratch, and started streaming on Twitch. This is honestly one of the most fun things I've ever done. After ~2 months, I have some quality individuals hanging out in my chat watching me play. Which is super weird! Complete strangers are looking at me while I play video games and sometimes posting penis emoji (seriously, what do you think will happen if you click that link?) all over my stream! But it's awesome and SO MUCH FUN.

I want to get better at streaming. I am going to arbitrarily validate my success via my follower count. Right now I have 58. I would like to shoot for 1000 by the end of the year. I think that's doable if I keep at it, but definitely a tough goal to have (wouldn't be fun if it were easy). Plus, always nice to make new internet pals!

So there you go. Resolutions for the year. Wish me luck!