Created
July 12, 2017 19:58
-
-
Save AugmentedFifth/afdd73660f1936780d4935d49a88fd4f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<title>Linux-compatible multiplayer games that don't suck</title> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet"> | |
<style> | |
body { | |
padding: 10px 20% 0 20%; | |
} | |
th { | |
border: 1px solid #444; | |
padding: 0; | |
margin: 0; | |
} | |
td { | |
border: 2px solid rgba(0, 0, 0, 0.25); | |
padding: 0; | |
margin: 0; | |
} | |
table.tablesorter { | |
font-family: "Roboto", arial, sans-serif; | |
background-color: #CDCDCD; | |
margin: 10px 0pt 15px; | |
font-size: 1rem; | |
width: 100%; | |
text-align: left; | |
} | |
table.tablesorter thead tr th, | |
table.tablesorter tfoot tr th { | |
background-color: #e6EEEE; | |
border: 1px solid #FFF; | |
font-size: 1rem; | |
padding: 4px; | |
} | |
table.tablesorter thead tr .header { | |
background-color: #378; | |
cursor: pointer; | |
} | |
table.tablesorter tbody td { | |
color: #3D3D3D; | |
padding: 4px; | |
background-color: #FFF; | |
vertical-align: top; | |
} | |
table.tablesorter tbody tr.odd td { | |
background-color: #F0F0F6; | |
} | |
table.tablesorter thead tr .headerSortUp { | |
background-color: #90b; | |
} | |
table.tablesorter thead tr .headerSortDown { | |
background-color: #29d; | |
} | |
table.tablesorter thead tr .headerSortDown, | |
table.tablesorter thead tr .headerSortUp { | |
background-color: #8dbdd8; | |
} | |
</style> | |
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha256-k2WSCIexGzOj3Euiig+TlR8gA0EmPjuc79OEeY5L45g=" | |
crossorigin="anonymous"></script> | |
<!-- tablesorter --> | |
<script> | |
(function ($) { | |
$.extend({ | |
tablesorter: new | |
function () { | |
var parsers = [], | |
widgets = []; | |
this.defaults = { | |
cssHeader: "header", | |
cssAsc: "headerSortUp", | |
cssDesc: "headerSortDown", | |
cssChildRow: "expand-child", | |
sortInitialOrder: "asc", | |
sortMultiSortKey: "shiftKey", | |
sortForce: null, | |
sortAppend: null, | |
sortLocaleCompare: true, | |
textExtraction: "simple", | |
parsers: {}, | |
widgets: [], | |
widgetZebra: { | |
css: ["even", "odd"] | |
}, | |
headers: {}, | |
widthFixed: false, | |
cancelSelection: true, | |
sortList: [], | |
headerList: [], | |
dateFormat: "us", | |
decimal: '/\.|\,/g', | |
onRenderHeader: null, | |
selectorHeaders: 'thead th', | |
debug: false | |
}; | |
function benchmark(s, d) { | |
log(s + "," + (new Date().getTime() - d.getTime()) + "ms"); | |
} | |
this.benchmark = benchmark; | |
function log(s) { | |
if (typeof console != "undefined" && typeof console.debug != "undefined") { | |
console.log(s); | |
} else { | |
alert(s); | |
} | |
} | |
function buildParserCache(table, $headers) { | |
if (table.config.debug) { | |
var parsersDebug = ""; | |
} | |
if (table.tBodies.length == 0) return; | |
var rows = table.tBodies[0].rows; | |
if (rows[0]) { | |
var list = [], | |
cells = rows[0].cells, | |
l = cells.length; | |
for (var i = 0; i < l; i++) { | |
var p = false; | |
if ($.metadata && ($($headers[i]).metadata() && $($headers[i]).metadata().sorter)) { | |
p = getParserById($($headers[i]).metadata().sorter); | |
} else if ((table.config.headers[i] && table.config.headers[i].sorter)) { | |
p = getParserById(table.config.headers[i].sorter); | |
} | |
if (!p) { | |
p = detectParserForColumn(table, rows, -1, i); | |
} | |
if (table.config.debug) { | |
parsersDebug += "column:" + i + " parser:" + p.id + "\n"; | |
} | |
list.push(p); | |
} | |
} | |
if (table.config.debug) { | |
log(parsersDebug); | |
} | |
return list; | |
}; | |
function detectParserForColumn(table, rows, rowIndex, cellIndex) { | |
var l = parsers.length, | |
node = false, | |
nodeValue = false, | |
keepLooking = true; | |
while (nodeValue == '' && keepLooking) { | |
rowIndex++; | |
if (rows[rowIndex]) { | |
node = getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex); | |
nodeValue = trimAndGetNodeText(table.config, node); | |
if (table.config.debug) { | |
log('Checking if value was empty on row:' + rowIndex); | |
} | |
} else { | |
keepLooking = false; | |
} | |
} | |
for (var i = 1; i < l; i++) { | |
if (parsers[i].is(nodeValue, table, node)) { | |
return parsers[i]; | |
} | |
} | |
return parsers[0]; | |
} | |
function getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex) { | |
return rows[rowIndex].cells[cellIndex]; | |
} | |
function trimAndGetNodeText(config, node) { | |
return $.trim(getElementText(config, node)); | |
} | |
function getParserById(name) { | |
var l = parsers.length; | |
for (var i = 0; i < l; i++) { | |
if (parsers[i].id.toLowerCase() == name.toLowerCase()) { | |
return parsers[i]; | |
} | |
} | |
return false; | |
} | |
function buildCache(table) { | |
if (table.config.debug) { | |
var cacheTime = new Date(); | |
} | |
var totalRows = (table.tBodies[0] && table.tBodies[0].rows.length) || 0, | |
totalCells = (table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length) || 0, | |
parsers = table.config.parsers, | |
cache = { | |
row: [], | |
normalized: [] | |
}; | |
for (var i = 0; i < totalRows; ++i) { | |
var c = $(table.tBodies[0].rows[i]), | |
cols = []; | |
if (c.hasClass(table.config.cssChildRow)) { | |
cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add(c); | |
continue; | |
} | |
cache.row.push(c); | |
for (var j = 0; j < totalCells; ++j) { | |
cols.push(parsers[j].format(getElementText(table.config, c[0].cells[j]), table, c[0].cells[j])); | |
} | |
cols.push(cache.normalized.length); | |
cache.normalized.push(cols); | |
cols = null; | |
}; | |
if (table.config.debug) { | |
benchmark("Building cache for " + totalRows + " rows:", cacheTime); | |
} | |
return cache; | |
}; | |
function getElementText(config, node) { | |
var text = ""; | |
if (!node) return ""; | |
if (!config.supportsTextContent) config.supportsTextContent = node.textContent || false; | |
if (config.textExtraction == "simple") { | |
if (config.supportsTextContent) { | |
text = node.textContent; | |
} else { | |
if (node.childNodes[0] && node.childNodes[0].hasChildNodes()) { | |
text = node.childNodes[0].innerHTML; | |
} else { | |
text = node.innerHTML; | |
} | |
} | |
} else { | |
if (typeof (config.textExtraction) == "function") { | |
text = config.textExtraction(node); | |
} else { | |
text = $(node).text(); | |
} | |
} | |
return text; | |
} | |
function appendToTable(table, cache) { | |
if (table.config.debug) { | |
var appendTime = new Date() | |
} | |
var c = cache, | |
r = c.row, | |
n = c.normalized, | |
totalRows = n.length, | |
checkCell = (n[0].length - 1), | |
tableBody = $(table.tBodies[0]), | |
rows = []; | |
for (var i = 0; i < totalRows; i++) { | |
var pos = n[i][checkCell]; | |
rows.push(r[pos]); | |
if (!table.config.appender) { | |
var l = r[pos].length; | |
for (var j = 0; j < l; j++) { | |
tableBody[0].appendChild(r[pos][j]); | |
} | |
} | |
} | |
if (table.config.appender) { | |
table.config.appender(table, rows); | |
} | |
rows = null; | |
if (table.config.debug) { | |
benchmark("Rebuilt table:", appendTime); | |
} | |
applyWidget(table); | |
setTimeout(function () { | |
$(table).trigger("sortEnd"); | |
}, 0); | |
}; | |
function buildHeaders(table) { | |
if (table.config.debug) { | |
var time = new Date(); | |
} | |
var meta = ($.metadata) ? true : false; | |
var header_index = computeTableHeaderCellIndexes(table); | |
$tableHeaders = $(table.config.selectorHeaders, table).each(function (index) { | |
this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex]; | |
this.order = formatSortingOrder(table.config.sortInitialOrder); | |
this.count = this.order; | |
if (checkHeaderMetadata(this) || checkHeaderOptions(table, index)) this.sortDisabled = true; | |
if (checkHeaderOptionsSortingLocked(table, index)) this.order = this.lockedOrder = | |
checkHeaderOptionsSortingLocked(table, index); | |
if (!this.sortDisabled) { | |
var $th = $(this).addClass(table.config.cssHeader); | |
if (table.config.onRenderHeader) table.config.onRenderHeader.apply($th); | |
} | |
table.config.headerList[index] = this; | |
}); | |
if (table.config.debug) { | |
benchmark("Built headers:", time); | |
log($tableHeaders); | |
} | |
return $tableHeaders; | |
}; | |
function computeTableHeaderCellIndexes(t) { | |
var matrix = []; | |
var lookup = {}; | |
var thead = t.getElementsByTagName('THEAD')[0]; | |
var trs = thead.getElementsByTagName('TR'); | |
for (var i = 0; i < trs.length; i++) { | |
var cells = trs[i].cells; | |
for (var j = 0; j < cells.length; j++) { | |
var c = cells[j]; | |
var rowIndex = c.parentNode.rowIndex; | |
var cellId = rowIndex + "-" + c.cellIndex; | |
var rowSpan = c.rowSpan || 1; | |
var colSpan = c.colSpan || 1 | |
var firstAvailCol; | |
if (typeof (matrix[rowIndex]) == "undefined") { | |
matrix[rowIndex] = []; | |
} | |
for (var k = 0; k < matrix[rowIndex].length + 1; k++) { | |
if (typeof (matrix[rowIndex][k]) == "undefined") { | |
firstAvailCol = k; | |
break; | |
} | |
} | |
lookup[cellId] = firstAvailCol; | |
for (var k = rowIndex; k < rowIndex + rowSpan; k++) { | |
if (typeof (matrix[k]) == "undefined") { | |
matrix[k] = []; | |
} | |
var matrixrow = matrix[k]; | |
for (var l = firstAvailCol; l < firstAvailCol + colSpan; l++) { | |
matrixrow[l] = "x"; | |
} | |
} | |
} | |
} | |
return lookup; | |
} | |
function checkCellColSpan(table, rows, row) { | |
var arr = [], | |
r = table.tHead.rows, | |
c = r[row].cells; | |
for (var i = 0; i < c.length; i++) { | |
var cell = c[i]; | |
if (cell.colSpan > 1) { | |
arr = arr.concat(checkCellColSpan(table, headerArr, row++)); | |
} else { | |
if (table.tHead.length == 1 || (cell.rowSpan > 1 || !r[row + 1])) { | |
arr.push(cell); | |
} | |
} | |
} | |
return arr; | |
}; | |
function checkHeaderMetadata(cell) { | |
if (($.metadata) && ($(cell).metadata().sorter === false)) { | |
return true; | |
}; | |
return false; | |
} | |
function checkHeaderOptions(table, i) { | |
if ((table.config.headers[i]) && (table.config.headers[i].sorter === false)) { | |
return true; | |
}; | |
return false; | |
} | |
function checkHeaderOptionsSortingLocked(table, i) { | |
if ((table.config.headers[i]) && (table.config.headers[i].lockedOrder)) return table.config.headers[i] | |
.lockedOrder; | |
return false; | |
} | |
function applyWidget(table) { | |
var c = table.config.widgets; | |
var l = c.length; | |
for (var i = 0; i < l; i++) { | |
getWidgetById(c[i]).format(table); | |
} | |
} | |
function getWidgetById(name) { | |
var l = widgets.length; | |
for (var i = 0; i < l; i++) { | |
if (widgets[i].id.toLowerCase() == name.toLowerCase()) { | |
return widgets[i]; | |
} | |
} | |
}; | |
function formatSortingOrder(v) { | |
if (typeof (v) != "Number") { | |
return (v.toLowerCase() == "desc") ? 1 : 0; | |
} else { | |
return (v == 1) ? 1 : 0; | |
} | |
} | |
function isValueInArray(v, a) { | |
var l = a.length; | |
for (var i = 0; i < l; i++) { | |
if (a[i][0] == v) { | |
return true; | |
} | |
} | |
return false; | |
} | |
function setHeadersCss(table, $headers, list, css) { | |
$headers.removeClass(css[0]).removeClass(css[1]); | |
var h = []; | |
$headers.each(function (offset) { | |
if (!this.sortDisabled) { | |
h[this.column] = $(this); | |
} | |
}); | |
var l = list.length; | |
for (var i = 0; i < l; i++) { | |
h[list[i][0]].addClass(css[list[i][1]]); | |
} | |
} | |
function fixColumnWidth(table, $headers) { | |
var c = table.config; | |
if (c.widthFixed) { | |
var colgroup = $('<colgroup>'); | |
$("tr:first td", table.tBodies[0]).each(function () { | |
colgroup.append($('<col>').css('width', $(this).width())); | |
}); | |
$(table).prepend(colgroup); | |
}; | |
} | |
function updateHeaderSortCount(table, sortList) { | |
var c = table.config, | |
l = sortList.length; | |
for (var i = 0; i < l; i++) { | |
var s = sortList[i], | |
o = c.headerList[s[0]]; | |
o.count = s[1]; | |
o.count++; | |
} | |
} | |
function multisort(table, sortList, cache) { | |
if (table.config.debug) { | |
var sortTime = new Date(); | |
} | |
var dynamicExp = "var sortWrapper = function(a,b) {", | |
l = sortList.length; | |
for (var i = 0; i < l; i++) { | |
var c = sortList[i][0]; | |
var order = sortList[i][1]; | |
var s = (table.config.parsers[c].type == "text") ? ((order == 0) ? makeSortFunction("text", "asc", | |
c) : makeSortFunction("text", "desc", c)) : ((order == 0) ? makeSortFunction("numeric", "asc", | |
c) : makeSortFunction("numeric", "desc", c)); | |
var e = "e" + i; | |
dynamicExp += "var " + e + " = " + s; | |
dynamicExp += "if(" + e + ") { return " + e + "; } "; | |
dynamicExp += "else { "; | |
} | |
var orgOrderCol = cache.normalized[0].length - 1; | |
dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];"; | |
for (var i = 0; i < l; i++) { | |
dynamicExp += "}; "; | |
} | |
dynamicExp += "return 0; "; | |
dynamicExp += "}; "; | |
if (table.config.debug) { | |
benchmark("Evaling expression:" + dynamicExp, new Date()); | |
} | |
eval(dynamicExp); | |
cache.normalized.sort(sortWrapper); | |
if (table.config.debug) { | |
benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time:", sortTime); | |
} | |
return cache; | |
}; | |
function makeSortFunction(type, direction, index) { | |
var a = "a[" + index + "]", | |
b = "b[" + index + "]"; | |
if (type == 'text' && direction == 'asc') { | |
return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + | |
" === null ? Number.NEGATIVE_INFINITY : (" + a + " < " + b + ") ? -1 : 1 )));"; | |
} else if (type == 'text' && direction == 'desc') { | |
return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + | |
" === null ? Number.NEGATIVE_INFINITY : (" + b + " < " + a + ") ? -1 : 1 )));"; | |
} else if (type == 'numeric' && direction == 'asc') { | |
return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + | |
" === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + a + | |
" - " + b + "));"; | |
} else if (type == 'numeric' && direction == 'desc') { | |
return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + | |
" === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + b + | |
" - " + a + "));"; | |
} | |
}; | |
function makeSortText(i) { | |
return "((a[" + i + "] < b[" + i + "]) ? -1 : ((a[" + i + "] > b[" + i + "]) ? 1 : 0));"; | |
}; | |
function makeSortTextDesc(i) { | |
return "((b[" + i + "] < a[" + i + "]) ? -1 : ((b[" + i + "] > a[" + i + "]) ? 1 : 0));"; | |
}; | |
function makeSortNumeric(i) { | |
return "a[" + i + "]-b[" + i + "];"; | |
}; | |
function makeSortNumericDesc(i) { | |
return "b[" + i + "]-a[" + i + "];"; | |
}; | |
function sortText(a, b) { | |
if (table.config.sortLocaleCompare) return a.localeCompare(b); | |
return ((a < b) ? -1 : ((a > b) ? 1 : 0)); | |
}; | |
function sortTextDesc(a, b) { | |
if (table.config.sortLocaleCompare) return b.localeCompare(a); | |
return ((b < a) ? -1 : ((b > a) ? 1 : 0)); | |
}; | |
function sortNumeric(a, b) { | |
return a - b; | |
}; | |
function sortNumericDesc(a, b) { | |
return b - a; | |
}; | |
function getCachedSortType(parsers, i) { | |
return parsers[i].type; | |
}; | |
this.construct = function (settings) { | |
return this.each(function () { | |
if (!this.tHead || !this.tBodies) return; | |
var $this, $document, $headers, cache, config, shiftDown = 0, | |
sortOrder; | |
this.config = {}; | |
config = $.extend(this.config, $.tablesorter.defaults, settings); | |
$this = $(this); | |
$.data(this, "tablesorter", config); | |
$headers = buildHeaders(this); | |
this.config.parsers = buildParserCache(this, $headers); | |
cache = buildCache(this); | |
var sortCSS = [config.cssDesc, config.cssAsc]; | |
fixColumnWidth(this); | |
$headers.click(function (e) { | |
var totalRows = ($this[0].tBodies[0] && $this[0].tBodies[0].rows.length) || 0; | |
if (!this.sortDisabled && totalRows > 0) { | |
$this.trigger("sortStart"); | |
var $cell = $(this); | |
var i = this.column; | |
this.order = this.count++ % 2; | |
if (this.lockedOrder) this.order = this.lockedOrder; | |
if (!e[config.sortMultiSortKey]) { | |
config.sortList = []; | |
if (config.sortForce != null) { | |
var a = config.sortForce; | |
for (var j = 0; j < a.length; j++) { | |
if (a[j][0] != i) { | |
config.sortList.push(a[j]); | |
} | |
} | |
} | |
config.sortList.push([i, this.order]); | |
} else { | |
if (isValueInArray(i, config.sortList)) { | |
for (var j = 0; j < config.sortList.length; j++) { | |
var s = config.sortList[j], | |
o = config.headerList[s[0]]; | |
if (s[0] == i) { | |
o.count = s[1]; | |
o.count++; | |
s[1] = o.count % 2; | |
} | |
} | |
} else { | |
config.sortList.push([i, this.order]); | |
} | |
}; | |
setTimeout(function () { | |
setHeadersCss($this[0], $headers, config.sortList, sortCSS); | |
appendToTable($this[0], multisort($this[0], config.sortList, cache)); | |
}, 1); | |
return false; | |
} | |
}).mousedown(function () { | |
if (config.cancelSelection) { | |
this.onselectstart = function () { | |
return false | |
}; | |
return false; | |
} | |
}); | |
$this.bind("update", function () { | |
var me = this; | |
setTimeout(function () { | |
me.config.parsers = buildParserCache(me, $headers); | |
cache = buildCache(me); | |
}, 1); | |
}).bind("updateCell", function (e, cell) { | |
var config = this.config; | |
var pos = [(cell.parentNode.rowIndex - 1), cell.cellIndex]; | |
cache.normalized[pos[0]][pos[1]] = config.parsers[pos[1]].format(getElementText(config, | |
cell), cell); | |
}).bind("sorton", function (e, list) { | |
$(this).trigger("sortStart"); | |
config.sortList = list; | |
var sortList = config.sortList; | |
updateHeaderSortCount(this, sortList); | |
setHeadersCss(this, $headers, sortList, sortCSS); | |
appendToTable(this, multisort(this, sortList, cache)); | |
}).bind("appendCache", function () { | |
appendToTable(this, cache); | |
}).bind("applyWidgetId", function (e, id) { | |
getWidgetById(id).format(this); | |
}).bind("applyWidgets", function () { | |
applyWidget(this); | |
}); | |
if ($.metadata && ($(this).metadata() && $(this).metadata().sortlist)) { | |
config.sortList = $(this).metadata().sortlist; | |
} | |
if (config.sortList.length > 0) { | |
$this.trigger("sorton", [config.sortList]); | |
} | |
applyWidget(this); | |
}); | |
}; | |
this.addParser = function (parser) { | |
var l = parsers.length, | |
a = true; | |
for (var i = 0; i < l; i++) { | |
if (parsers[i].id.toLowerCase() == parser.id.toLowerCase()) { | |
a = false; | |
} | |
} | |
if (a) { | |
parsers.push(parser); | |
}; | |
}; | |
this.addWidget = function (widget) { | |
widgets.push(widget); | |
}; | |
this.formatFloat = function (s) { | |
var i = parseFloat(s); | |
return (isNaN(i)) ? 0 : i; | |
}; | |
this.formatInt = function (s) { | |
var i = parseInt(s); | |
return (isNaN(i)) ? 0 : i; | |
}; | |
this.isDigit = function (s, config) { | |
return /^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g, ''))); | |
}; | |
this.clearTableBody = function (table) { | |
if ($.browser.msie) { | |
function empty() { | |
while (this.firstChild) this.removeChild(this.firstChild); | |
} | |
empty.apply(table.tBodies[0]); | |
} else { | |
table.tBodies[0].innerHTML = ""; | |
} | |
}; | |
} | |
}); | |
$.fn.extend({ | |
tablesorter: $.tablesorter.construct | |
}); | |
var ts = $.tablesorter; | |
ts.addParser({ | |
id: "text", | |
is: function (s) { | |
return true; | |
}, | |
format: function (s) { | |
return $.trim(s.toLocaleLowerCase()); | |
}, | |
type: "text" | |
}); | |
ts.addParser({ | |
id: "digit", | |
is: function (s, table) { | |
var c = table.config; | |
return $.tablesorter.isDigit(s, c); | |
}, | |
format: function (s) { | |
return $.tablesorter.formatFloat(s); | |
}, | |
type: "numeric" | |
}); | |
ts.addParser({ | |
id: "currency", | |
is: function (s) { | |
return /^[£$€?.]/.test(s); | |
}, | |
format: function (s) { | |
return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g), "")); | |
}, | |
type: "numeric" | |
}); | |
ts.addParser({ | |
id: "ipAddress", | |
is: function (s) { | |
return /^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s); | |
}, | |
format: function (s) { | |
var a = s.split("."), | |
r = "", | |
l = a.length; | |
for (var i = 0; i < l; i++) { | |
var item = a[i]; | |
if (item.length == 2) { | |
r += "0" + item; | |
} else { | |
r += item; | |
} | |
} | |
return $.tablesorter.formatFloat(r); | |
}, | |
type: "numeric" | |
}); | |
ts.addParser({ | |
id: "url", | |
is: function (s) { | |
return /^(https?|ftp|file):\/\/$/.test(s); | |
}, | |
format: function (s) { | |
return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//), '')); | |
}, | |
type: "text" | |
}); | |
ts.addParser({ | |
id: "isoDate", | |
is: function (s) { | |
return /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s); | |
}, | |
format: function (s) { | |
return $.tablesorter.formatFloat((s != "") ? new Date(s.replace(new RegExp(/-/g), "/")).getTime() : | |
"0"); | |
}, | |
type: "numeric" | |
}); | |
ts.addParser({ | |
id: "percent", | |
is: function (s) { | |
return /\%$/.test($.trim(s)); | |
}, | |
format: function (s) { | |
return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g), "")); | |
}, | |
type: "numeric" | |
}); | |
ts.addParser({ | |
id: "usLongDate", | |
is: function (s) { | |
return s.match(new RegExp( | |
/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/ | |
)); | |
}, | |
format: function (s) { | |
return $.tablesorter.formatFloat(new Date(s).getTime()); | |
}, | |
type: "numeric" | |
}); | |
ts.addParser({ | |
id: "shortDate", | |
is: function (s) { | |
return /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s); | |
}, | |
format: function (s, table) { | |
var c = table.config; | |
s = s.replace(/\-/g, "/"); | |
if (c.dateFormat == "us") { | |
s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$1/$2"); | |
} else if (c.dateFormat == "uk") { | |
s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$2/$1"); | |
} else if (c.dateFormat == "dd/mm/yy" || c.dateFormat == "dd-mm-yy") { | |
s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/, "$1/$2/$3"); | |
} | |
return $.tablesorter.formatFloat(new Date(s).getTime()); | |
}, | |
type: "numeric" | |
}); | |
ts.addParser({ | |
id: "time", | |
is: function (s) { | |
return /^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s); | |
}, | |
format: function (s) { | |
return $.tablesorter.formatFloat(new Date("2000/01/01 " + s).getTime()); | |
}, | |
type: "numeric" | |
}); | |
ts.addParser({ | |
id: "metadata", | |
is: function (s) { | |
return false; | |
}, | |
format: function (s, table, cell) { | |
var c = table.config, | |
p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName; | |
return $(cell).metadata()[p]; | |
}, | |
type: "numeric" | |
}); | |
ts.addWidget({ | |
id: "zebra", | |
format: function (table) { | |
if (table.config.debug) { | |
var time = new Date(); | |
} | |
var $tr, row = -1, | |
odd; | |
$("tr:visible", table.tBodies[0]).each(function (i) { | |
$tr = $(this); | |
if (!$tr.hasClass(table.config.cssChildRow)) row++; | |
odd = (row % 2 == 0); | |
$tr.removeClass(table.config.widgetZebra.css[odd ? 0 : 1]).addClass(table.config.widgetZebra.css[ | |
odd ? 1 : 0]) | |
}); | |
if (table.config.debug) { | |
$.tablesorter.benchmark("Applying Zebra widget", time); | |
} | |
} | |
}); | |
})(jQuery); | |
</script> | |
<!-- smartquotes --> | |
<script> | |
! function (e, n) { | |
"function" == typeof define && define.amd ? define(n) : "object" == typeof exports ? module.exports = n() : e.smartquotes = | |
n() | |
}(this, function () { | |
function e(n) { | |
if ("undefined" == typeof document || void 0 !== n) return "string" == typeof n ? e.string(n) : e.element(n); | |
var t = function () { | |
e.element(document.body) | |
}; | |
if ("loading" !== document.readyState) t(); | |
else if (document.addEventListener) document.addEventListener("DOMContentLoaded", t, !1); | |
else var u = setInterval(function () { | |
"loading" !== document.readyState && (clearInterval(u), t()) | |
}, 10) | |
} | |
return e.string = function (e, n) { | |
return e.replace(/'''/g, "‴" + (n ? "" : "")).replace(/(\W|^)"(\w)/g, "$1“$2").replace( | |
/(\u201c[^"]*)"([^"]*$|[^\u201c"]*\u201c)/g, "$1”$2").replace(/([^0-9])"/g, "$1”").replace(/''/g, "″" + | |
(n ? "" : "")).replace(/(\W|^)'(\S)/g, "$1‘$2").replace(/([a-z])'([a-z])/gi, "$1’$2").replace( | |
/(\u2018)([0-9]{2}[^\u2019]*)(\u2018([^0-9]|$)|$|\u2019[a-z])/gi, "’$2$3").replace( | |
/((\u2018[^']*)|[a-z])'([^0-9]|$)/gi, "$1’$3").replace( | |
/(\B|^)\u2018(?=([^\u2018\u2019]*\u2019\b)*([^\u2018\u2019]*\B\W[\u2018\u2019]\b|[^\u2018\u2019]*$))/gi, | |
"$1’").replace(/"/g, "″").replace(/'/g, "′") | |
}, e.element = function (n) { | |
function t(n) { | |
if (-1 === ["CODE", "PRE", "SCRIPT", "STYLE"].indexOf(n.nodeName.toUpperCase())) { | |
var a, o, d, l = n.childNodes, | |
i = [], | |
c = ""; | |
for (a = 0; a < l.length; a++)(o = l[a]).nodeType === r || "#text" === o.nodeName ? (i.push([o, c.length]), | |
c += o.nodeValue || o.value) : o.childNodes && o.childNodes.length && (c += t(o)); | |
c = e.string(c, !0); | |
for (a in i)(d = i[a])[0].nodeValue ? d[0].nodeValue = u(c, d[0].nodeValue, d[1]) : d[0].value && (d[0] | |
.value = u(c, d[0].value, d[1])); | |
return c | |
} | |
} | |
function u(e, n, t) { | |
return e.substr(t, n.length).replace("", "") | |
} | |
var r = "undefined" != typeof Element && Element.TEXT_NODE || 3; | |
return t(n), n | |
}, e | |
}); | |
</script> | |
<script> | |
$(document).ready(function () { | |
$("#main-table").tablesorter({ | |
sortList: [ | |
[0, 0] | |
] | |
}); | |
smartquotes(); | |
}); | |
</script> | |
</head> | |
<body> | |
<table id="main-table" class="tablesorter"> | |
<thead> | |
<tr> | |
<th>Name</th> | |
<th>Player arity</th> | |
<th>Price</th> | |
<th>Genre</th> | |
<th>Description</th> | |
<th>Notes</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>Stunt Rally</td> | |
<td>1 or a few players</td> | |
<td>Free</td> | |
<td>Racing</td> | |
<td>Rally racing plain and simple.</td> | |
<td>No longer maintained/developed.</td> | |
</tr> | |
<tr> | |
<td>Toontown Rewritten</td> | |
<td>MMO</td> | |
<td>Free</td> | |
<td>MMORPG</td> | |
<td>It's Toontown.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Spiral Knights</td> | |
<td>MMO</td> | |
<td>Free</td> | |
<td>MMORPG</td> | |
<td>Play as cute little knight duders in a game with fairly polished 3D graphics.</td> | |
<td>Java lol</td> | |
</tr> | |
<tr> | |
<td>Toribash</td> | |
<td>1-2 players</td> | |
<td>Free</td> | |
<td>Fighting, Physics</td> | |
<td><strong>Extremely</strong> unique turn-based and physics-based fighting game.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Tesseract</td> | |
<td>Handful of players on each team</td> | |
<td>Free</td> | |
<td>FPS</td> | |
<td>Modern and polished take on instagib-style FPS.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>AssaultCube Reloaded</td> | |
<td>Handful of players on each team</td> | |
<td>Free</td> | |
<td>FPS</td> | |
<td>Classic Counter-Strike-like FPS, based off of the well-known "Cube" or "Sauerbraten" engine.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Minecraft</td> | |
<td>1+ players</td> | |
<td>Free</td> | |
<td>Minecraft</td> | |
<td>Minecraft.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Risk of Rain</td> | |
<td>1-4 players</td> | |
<td>$10</td> | |
<td>Platformer, Permadeath</td> | |
<td>Killer aesthetic and permadeath in a 2D cooperative platformer.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Armagetron Advanced</td> | |
<td>1-4 players</td> | |
<td>Free</td> | |
<td>Arcade</td> | |
<td>Well-known and polished snake-based game inspired by "Tron".</td> | |
<td>FLOSS</td> | |
</tr> | |
<tr> | |
<td>Unturned</td> | |
<td>1+ players</td> | |
<td>Free</td> | |
<td>Survival</td> | |
<td>Survive in a large blocky world where zombies try to kill you dead.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Dota 2</td> | |
<td>5v5 primarily, also co-op or smaller teams</td> | |
<td>Free</td> | |
<td>ARTS</td> | |
<td>dodo lol</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Civilization V</td> | |
<td>1-12 players or so</td> | |
<td>Already purchased</td> | |
<td>Turn-Based Strategy</td> | |
<td>Roleplay as an entire nationstate.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Starbound</td> | |
<td>1+ players</td> | |
<td>Already purchased</td> | |
<td>Action/Adventure</td> | |
<td>Potato Grids: The Game.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>ibb & obb</td> | |
<td>2-player co-op</td> | |
<td>Already purchased</td> | |
<td>Puzzle Platformer</td> | |
<td>Play cute little duders and work together to solve all the puzzles and attain freedom.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Team Fortress 2</td> | |
<td>Handful of players per team, or co-op</td> | |
<td>Free</td> | |
<td>FPS</td> | |
<td>Class-based FPS with <code>a e s t h e t i c s</code>.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Counter-Strike: Source</td> | |
<td>Handful of players per team</td> | |
<td>$20, I already have it</td> | |
<td>FPS</td> | |
<td>The classic FPS set in the very early 21st century.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Don't Starve Together</td> | |
<td>1+ players</td> | |
<td>Already purchased</td> | |
<td>Survival</td> | |
<td>What if you had to survive just to be part of a Tim Burton animated film?</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Rust</td> | |
<td>2-50 players per server</td> | |
<td>Already purchased</td> | |
<td>Survival</td> | |
<td>What if you were naked but also you were getting shot and starving?</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Nova Blitz</td> | |
<td>1 player or 1v1</td> | |
<td>Free</td> | |
<td>TCG</td> | |
<td>Hearthstone knockoff with Linux support, reviewers like it.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Rocket League</td> | |
<td>1-6 players, competitive</td> | |
<td>$20</td> | |
<td>Action, Racing, Sports</td> | |
<td>Play soccer with a 3 meter radius ball but also you play as a car with rockets attached.</td> | |
<td>I've had my eye on this one for a while, fun game.</td> | |
</tr> | |
<tr> | |
<td>Counter-Strike: Global Offensive</td> | |
<td>Handful of players per team</td> | |
<td>$15</td> | |
<td>FPS</td> | |
<td>cOuNter StRiKe</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Terraria</td> | |
<td>1+ players</td> | |
<td>$10</td> | |
<td>Minecraft clone</td> | |
<td>Minecraft clone, 2D</td> | |
<td>Probably not as cool as just playing starchbound.</td> | |
</tr> | |
<tr> | |
<td>Portal 2</td> | |
<td>1-2 players</td> | |
<td>$20</td> | |
<td>First Person Puzzle</td> | |
<td>Portal. It's Portal.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>ShellShock Live</td> | |
<td>Handful of players</td> | |
<td>$7</td> | |
<td>Tank Strategy, 2D</td> | |
<td>Kinda like Worms but with colorful cartoon tanks instead.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Divinity: Original Sin</td> | |
<td>1-2 players</td> | |
<td>$40</td> | |
<td>D&D</td> | |
<td>Yeah it's like those old D&D games but even better somehow.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>ONRAID</td> | |
<td>1+ players</td> | |
<td>Free</td> | |
<td>2D Shooter</td> | |
<td>Mishmashed aesthetic 2D shooter with pretty good reviews.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Faeria</td> | |
<td>1-2 players</td> | |
<td>Free</td> | |
<td>TCG</td> | |
<td>Sorta a Hearthstone ripoff, but looks a bit more original and polished.</td> | |
<td>Very good reviews.</td> | |
</tr> | |
<tr> | |
<td>UniBall</td> | |
<td>Handful of players per team</td> | |
<td>Free</td> | |
<td>Sports, 2D</td> | |
<td>It's space hockey, in 2D.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>StarBreak</td> | |
<td>MMO</td> | |
<td>Free</td> | |
<td>Skill-based action platformer MMO.</td> | |
<td>Cheesy aesthetic, good reviews</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Istrolid</td> | |
<td>1+ players, co-op</td> | |
<td>Free</td> | |
<td>Shipbuilding, 2D Action Strategy</td> | |
<td>Build ships together and shoot up spacey bad guys together.</td> | |
<td>Looks hot.</td> | |
</tr> | |
<tr> | |
<td>Age of Conquest IV</td> | |
<td>Handful of players, can be co-op</td> | |
<td>Free</td> | |
<td>Turn-Based Strategy</td> | |
<td>It's RISK, but for your computer.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Teeworlds</td> | |
<td>Handful of players</td> | |
<td>Free</td> | |
<td>Platformer, 2D, Deathmatch/CTF</td> | |
<td>Fast paced and cartoony. Fairly polished.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>RPG MO</td> | |
<td>MMO</td> | |
<td>Free</td> | |
<td>MMORPG (Fantasy)</td> | |
<td>Ultima Online and Runescape had a free-to-play, early-access baby.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Fistful of Frags</td> | |
<td>2+ players</td> | |
<td>Free</td> | |
<td>FPS</td> | |
<td>Highly reviewed wild west shooter.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>No More Room In Hell</td> | |
<td>2+, co-op</td> | |
<td>Free</td> | |
<td>FPS, Zombie</td> | |
<td>A take on the classic "Dawn of the Dead".</td> | |
<td>Highly reviewed, but Ichizo happens to recommend <em>against</em> it.</td> | |
</tr> | |
<tr> | |
<td>SNOW</td> | |
<td>1+ players</td> | |
<td>Free</td> | |
<td>Sports</td> | |
<td>Open world winter sports. Fall off your snowboard, virtually.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Awesomenauts</td> | |
<td>1+ players, co-op</td> | |
<td>Free</td> | |
<td>ARTS, 2D</td> | |
<td>Someone had the guts to make a 2D ARTS, and it's got very cartoony but coherent aesthetic and lots of praise.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Altitude</td> | |
<td>1+ players</td> | |
<td>Free</td> | |
<td>Action, 2D</td> | |
<td>Dogfighting in 2D; skill-based.</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>Pirates, Vikings, and Knights II</td> | |
<td>Handful of players</td> | |
<td>Free</td> | |
<td>FPS</td> | |
<td>A glorious HL2 mod that is exactly what it sounds like.</td> | |
<td></td> | |
</tr> | |
</tbody> | |
</table> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment