Calculating Swiss Record Required to Reach Elimination Rounds

Here’s some Python that calculates how many players will reach each record in a Swiss tournament with a Top 8 or similar cut.

from typing import Sequence

# Math from https://www.mtgsalvation.com/forums/magic-fundamentals/magic-general/325775-making-the-cut-in-swiss-tournaments
def swisscalc(num_players: int, num_rounds: int, num_elimination_rounds: int) -> Sequence[int]:
    num_players_in_elimination_rounds = 2 ** num_elimination_rounds
    base = num_players / (2 ** num_rounds)
    num_players_by_losses = [0] * (num_rounds + 1)
    multiplier = 1.0
    total_so_far = 0
    record_required = None
    for losses in range(0, num_rounds + 1):
        wins = num_rounds - losses
        numerator = wins + 1
        denominator = losses
        if denominator > 0:
            multiplier *= (numerator / denominator)
        num_players_by_losses[losses] = base * multiplier
        if not record_required and num_players_in_elimination_rounds:
            total_so_far += num_players_by_losses[losses]
    return num_players_by_losses

Example usage:

$ python3
>>> rounds = 4
>>> r = swisscalc(24, rounds, 3)
>>> for losses in range(len(r)):
...     print(f'{r[losses]} players at {rounds - losses}–{losses}')
... 
1.5 players at 4–0
6.0 players at 3–1
9.0 players at 2–2
6.0 players at 1–3
1.5 players at 0–4

git popclean

If you git stash when you have a bunch of local files that are ignored git stash pop will refuse to un-stash your saved changes. This command cleans that up.


git stash pop 2>&1 | grep already | cut -d' ' -f1 | xargs rm && git stash pop

Make a PUT request in Go (with JSON body)

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"time"
)

type MyRequest struct {
	Name  string `json:"name"`
	Color string `json:"color"`
	Size  int    `json:"size"`
}

type MyResponse struct {
	Status string `json:"status"`
}

func doRequest(httpMethod string, address string, requestBody MyRequest, responseBody *MyResponse) (err error) {
	j, err := json.Marshal(requestBody)
	if err != nil {
		return
	}
	req, err := http.NewRequest(httpMethod, address, bytes.NewReader(j))
	if err != nil {
		return
	}
	req.Header.Set("Content-type", "application/json")
	client := http.Client{Timeout: time.Second * 10}
	resp, err := client.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		return fmt.Errorf("Request failed with status %d", resp.StatusCode)
	}
	err = json.NewDecoder(resp.Body).Decode(responseBody)
	if err != nil {
		return
	}
	return
}

func main() {
	responseBody := new(MyResponse)
	err := doRequest("PUT", "https://example.com/endpoint", MyRequest{Name: "bakert", Color: "red", Size: 10}, responseBody)
	if err != nil {
		fmt.Println("Failed", err)
	} else {
		fmt.Println("Status", responseBody.Status)
	}
}

Keeping CDN-based js dependencies up to date

For python dependencies you can use a simple requirements.txt file and something like requires.io to keep all your dependencies at latest stable release automatically.

npm and webpack and friends can do a fine job of keeping your js dependencies up to date if you are prepared to bundle them into your js.

But what if you want to use the publicly-hosted CDN versions? There doesn’t seem to be anything available.

This is what I came up with. I’m not in love with it but it works ok so far!

It reads a file that looks like this:

jquery
jquery.hoverIntent
jquery.tablesorter
jquery.tablesorter:jquery.tablesorter.widgets
moment.js
moment-timezone:moment-timezone-with-data
Chart.js
react
react-dom

and emits a file that looks like this:

<script defer src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script defer src="//cdnjs.cloudflare.com/ajax/libs/jquery.hoverintent/1.10.0/jquery.hoverIntent.min.js"></script>
<script defer src="//cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.31.1/js/jquery.tablesorter.min.js"></script>
<script defer src="//cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.31.1/js/jquery.tablesorter.widgets.min.js"></script>
<script defer src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
<script defer src="//cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.26/moment-timezone-with-data.min.js"></script>
<script defer src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js"></script>
<script defer src="//cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script defer src="//cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>

Here’s the code, which lives at https://github.com/PennyDreadfulMTG/Penny-Dreadful-Tools/blob/master/maintenance/client_dependencies.py:

import re
import subprocess
from typing import List

from shared import fetch_tools
from shared.pd_exception import DoesNotExistException

PATH = 'shared_web/templates/jsdependencies.mustache'

def ad_hoc() -> None:
    tags = [fetch_script_tag(library) + '\n' for library in get_dependencies()]
    output = ''.join(tags)
    write_dependencies(output)
    send_pr_if_updated()

def get_dependencies() -> List[str]:
    f = open('shared_web/jsrequirements.txt', 'r')
    return [line.strip() for line in f.readlines()]

def write_dependencies(s: str) -> None:
    f = open(PATH, 'w')
    f.write(s)

def send_pr_if_updated() -> None:
    subprocess.call(['git', 'add', PATH])
    if subprocess.call(['git', 'commit', '-m', 'Update client dependencies.']) == 0:
        subprocess.call(['git', 'push'])
        subprocess.call(['hub', 'pull-request', '-b', 'master', '-m', 'Update client dependencies.', '-f'])

def fetch_script_tag(entry: str) -> str:
    parts = entry.split(':')
    library = parts[0]
    file = parts[0] if len(parts) == 1 else parts[1]
    info = fetch_tools.fetch_json(f'https://api.cdnjs.com/libraries/{library}')
    version = info.get('version')
    if not version and library.lower() != library:
        library = library.lower()
        info = fetch_tools.fetch_json(f'https://api.cdnjs.com/libraries/{library}')
        version = info.get('version')
    if not version:
        raise DoesNotExistException(f'Could not get version for {library}')
    path = None
    for a in info['assets']:
        if a.get('version') == version:
            for f in a['files']:
                if minified_path(f, file):
                    path = f
                    break
                if unminified_path(f, file):
                    path = f
    if not path:
        raise DoesNotExistException(f'Could not find file for {library}')
    return f'<script defer src="//cdnjs.cloudflare.com/ajax/libs/{library}/{version}/{path}"></script>'

def minified_path(path: str, library: str) -> bool:
    return test_path(path, library, '.min')

def unminified_path(path: str, library: str) -> bool:
    return test_path(path, library)

def test_path(path: str, library: str, required: str = '') -> bool:
    # CommonJS libs get us the error 'require is not defined' in the browser. See #6731.
    if 'cjs/' in path:
        return False
    name_without_js = library.replace('.js', '')
    regex = fr'{name_without_js}(.js)?(.production)?{required}.js

    return bool(re.search(regex, path, re.IGNORECASE))

Set Up a New Mac

Updated from a few years ago: http://bluebones.net/2015/12/things-i-absolutely-must-do-on-a-new-mac/

  • Upgrade to latest OSX.
  • Restore home directory from Backblaze. (Or at least dotfiles, .ssh dir, .vim dir, bin directory, ~/Application Support/Alfred 4/*, pwd.dsv.gpg, business, travel)
  • Dock: remove everything, hiding on, magnification on.
  • Sublime Text 3 (and add license key, set “trim_trailing_white_space_on_save”: true in sublime prefs).
  • Solarized theme for Sublime Text 3 (after installing Package Control).
  • subl commandline sublime.$ ln -s “/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl” ~/bin/subl
  • Sublime git ignorer (to have ST3 respect .gitignore in searches, etc.) via Package Control.
  • Hide Desktop icons.
    $ defaults write com.apple.finder CreateDesktop false
    $ killall Finder
  • Alfred (and give it Accessibility access, add license key).
  • Solarized theme for Terminal.
  • “Use Option as Meta Key” in Terminal to make left Alt work as Esc.
  • Use Ctrl-f7 or the Keyboard, Shortcuts in System Preferences to give Full Keyboard Access so that tab takes you to every button in a dialog, etc.
  • Discord.
  • Homebrew. (Installs OSX commandline tools.)
  • Change all .txt to open with Sublime Text.
  • Show hidden files in Finder. Cmd-Shift-.
  • WhatsApp.
  • 1Password.
  • Wine + MTGO: https://github.com/pauleve/docker-mtgo/wiki/macOS:-installing-MTGO-using-Wine
  • Chrome. (Log in to Chrome to get uBlock Origin and other extensions.)
  • Skitch.
  • mysql/mariadb (via homebrew).
  • rtm-cli (via homebrew).
  • Backblaze.

Read Properties File in Spring with Kotlin

import java.io.FileInputStream
import java.util.Properties
import org.springframework.util.ResourceUtils

object Configuration {
    private val properties: Properties = Properties()

    val databaseHost: String?
    val databaseName: String?
    val databaseUser: String?
    val databasePassword: String?
    val securitySecret: String

    init {
        val file = ResourceUtils.getFile("classpath:app.properties")
        val stream = FileInputStream(file)
        properties.load(stream)
        databaseHost = properties.getProperty("database.host")
        databaseName = properties.getProperty("database.name")
        databaseUser = properties.getProperty("database.user")
        databasePassword = properties.getProperty("database.password")
        securitySecret = properties.getProperty("security.secret")!!
    }
}

Random rows in MySQL with Reasonable Performance

Almost every answer on Stack Overflow for this is terrible on moderately complex or large real data – https://stackoverflow.com/questions/4329396/mysql-select-10-random-rows-from-600k-rows-fast.

Bad: ORDER BY RAND() LIMIT N

This means executing your query for the entire resultset and then ordering it and then chopping off the number you need. This can have truly dire performance.

Bad: SELECT MAX(id) FROM table
Don’t pick random numbers from 1 to MAX(id) – deleted rows will be null or result in you getting less rows than you want. Who says your id is even sequential/numeric?

OK: SELECT id FROM table

Then pick N at random (removing those already chosen if you don’t want duplicates) from the resultset in your chosen programming language.

If you just want one row from the db you can do this in SQL only as shown here: https://stackoverflow.com/a/31066058/375262. Doing N unique rows this way is left as an exercise for the reader.

If you have a gargantuan resultset even SELECT COUNT(*) might be slow. What would you do then?