Goodbye Manager’s Life

Vor über 5 Jahren habe ich Manager’s Life ins Leben gerufen, einerseits um ein cooles Browserspiel zu bauen, andererseits um (damals) neue Techniken/Technologie wie HTML5 2D Canavas auszuprobieren. Aus dieser Zeit sind auch einige Blog-Beiträge entstanden (zB: Sprechblasen mit HTML5 Canvas). Das Spiel war/ist technisch ziemlich cool, allerdings habe ich zu wenig Zeit in ein gutes Konzept und eine gute Spielmechanik gesteckt. Dadurch ist das Spiel nie richtig ins Rollen gekommen. Zwar habe ich es im Hintergrund immer weiter laufen lassen, aber heute ist Zeit das Spiel offline zu nehmen. Danke an alle Spieler und an alle die mitgeholfen haben Manager’s Life zu entwickeln.

Der Quellcode des Spiels ist auf Github verfügbar: agrafix/managerslife.

 

JavaScript: Bildschirmauflösung

Das window.screen-Objekt in JavaScript enthält einige nützliche Informationen über Bildschirmkonfiguration und -auflösung des Besuchers. Diese kann man zum Beispiel nutzen um eine Designweiche zu implementieren, oder Statistiken zu sammeln.

Folgende Informationen kann man abrufen:

console.log("Bildschirmbreite: " + window.screen.width);
console.log("Bildschirmhöhe: " + window.screen.height);
console.log("Bildschirmbreite ohne Browserinterface: " + window.screen.availWidth);
console.log("Bildschirmhöhe ohne Browserinterface: " + window.screen.availHeight);
console.log("Farbtiefe: " + window.screen.colorDepth);
console.log("Bits pro Pixel: " + window.screen.pixelDepth); // nur Firefox
 

RC4 Haskell und JavaScript

RC4 in JavaScript: (Quelle)

/*
 * RC4 symmetric cipher encryption/decryption
 *
 * @license Public Domain
 * @param string key - secret key for encryption/decryption
 * @param string str - string to be encrypted/decrypted
 * @return string
 */
function rc4(key, str) {
	var s = [], j = 0, x, res = '';
	for (var i = 0; i < 256; i++) {
		s[i] = i;
	}
	for (i = 0; i < 256; i++) {
		j = (j + s[i] + key.charCodeAt(i % key.length)) % 256;
		x = s[i];
		s[i] = s[j];
		s[j] = x;
	}
	i = 0;
	j = 0;
	for (var y = 0; y < str.length; y++) {
		i = (i + 1) % 256;
		j = (j + s[i]) % 256;
		x = s[i];
		s[i] = s[j];
		s[j] = x;
		res += String.fromCharCode(str.charCodeAt(y) ^ s[(s[i] + s[j]) % 256]);
	}
	return res;
}

RC4 in Haskell:

module RC4
(
 encode,
 decode
)
where

import Data.Map (Map, (!))
import qualified Data.Map as Map
import Data.Char (ord, chr)
import Data.Bits (xor)

encode :: String -> String -> String
encode = rc4

decode :: String -> String -> String
decode = rc4

initS = Map.fromList $ zip [0 .. 255] [0 .. 255]

rc4 :: String -> String -> String
rc4 key str =
    resultStr
    where
      (_, s) = foldl mkBox (0, initS) [0 .. 255]
      mkBox (inpJ, inpS) i =
          (j, s'')
          where
            j = (inpJ + (inpS ! i) + (ord (key !! (i `mod` (length key))))) `mod` 256
            x = inpS ! i
            s' = Map.insert i (inpS ! j) inpS
            s'' = Map.insert j x s'

      (resultStr, _, _, _) = result

      result = foldl core ("", 0, 0, s) [0 .. ((length str) - 1)]
      core (res, inpI, inpJ, inpS) y =
          (res', i, j, s'')
          where
            i = (inpI + 1) `mod` 256
            j = (inpJ + (inpS ! i)) `mod` 256
            x = inpS ! i
            s' =  Map.insert i (inpS ! j) inpS
            s'' = Map.insert j x s'
            k = ((s'' ! i) + (s'' ! j)) `mod` 256
            res' = res ++ [chr $ (ord (str !! y)) `xor` (s'' ! k)]
 

JavaScript: Duplikate aus Array entfernen

Kurzer JavaScript Code-Snippet, um Duplikate aus einem JavaScript-Array zu entfernen:

/**
 * Remove duplicates from array
 * 
 */
var removeDuplicates = function(t) {
    t.sort();

    var elems = {};
    var newArray = [];

    for (var i = 0, len = t.length; i < len; i++)  {
        elems[t[i]] = 0;
    }

    for (var elem in elems) {
        newArray.push(elem);
    }

    return newArray;
};
 

ReTI IDE & Simulator (Technische Informatik)

Da ich aktuell an der Veranstaltung Technische Informatik an der Uni teilnehme war es im letzten Übungsblatt Aufgabe Programm-Code für einen einfachen Prozessor (ReTI oder auch RESA) zu schreiben. Das vorgeschlagene Tool dafür (Neumi) kann entsprechenden Programm-Code zwar super visualisieren, ist allerdings beim „coden“ nicht sehr hilfreich da es kein vernünftiges Feedback bei Fehlern gibt und es auch keine gute Eingabemaske für den Code gibt. Deshalb hab ich mich hingesetzt und eine kleine HTML-JavaScript-IDE mit einer ReTI-Simulation in PHP zusammengebaut. Das ganze ist unter reti.agrafix.net zu finden! Ich habe auch eine kleine Dokumentation dazu geschrieben, es sollte sich also alles von selbst erklären.
Ein paar Worte vielleicht noch zur „Simulation“: Die Simulation ist eher ein Debugger, da der Code nicht assembliert sondern direkt interpretiert wird. Das ist notwendig um zielgenaues Feedback bei (Laufzeit-)Fehlern zu geben. Deshalb ist es nicht möglich auf Speicherzellen zuzugreifen, die Anweisungen/Befehle enthalten. (Mit dieser Einschränkung konnte man dennoch alle Aufgabenstellungen bis jetzt lösen 😉 ) Solange man das also nicht benötigt, kann der Simulator den korrekten Endspeicherzustand berechnen.

Zur ReTI IDE & Simulator

Für die HTML-JavaScript-IDE bzw. dessen Code-Editor habe ich übrigens auf das CodeMirror 2 Framework gebaut. Super Framework, leider muss man erstmal ein bisschen Dokumentation lesen bis man damit zu guten Ergebnisen kommt!

 

Keep your scope – jQuery.proxy

Wer kennt das nicht, man arbeitet mit jQuery und einem click-Event, und „verliert“ dadurch bei Objekten das „this“. Kleines Beispiel:

function MyObject() {
	this.hello = "Hello";
}

MyObject.prototype.clickHandler = function(event) {
	alert(this.hello);
};

var obj = new MyObject();

$('#clickMe').click(obj.clickHandler);

Wenn man jetzt auf ein gedachtes Element mit der ID „clickMe“ klickt, dann erscheint eine Fehlermeldung in JavaScript bzw. eine Alert-Box mit dem Inhalt „undefined“ – obwohl this.hello eigentlich doch „Hello“ sein sollte? Bei den jQuery Event-Handlern „verliert“ die Funktion ihr aktuelles Scope und das this zeigt dann auf den Sender des Events, in diesem Fall auf das Element mit der ID „clickMe“. Das ist zwar oft gewünscht, da man dieses nun zB mit $(this).text(‚Ich wurde geklicked‘); weiterverarbeiten kann, ist aber in meinem Beispiel oben sehr ungünstig. Deshalb bietet die jQuery-Library eine Funktion jQuery.proxy, welche eine Proxy-Funktion generiert mit der wir das Scope einer Funktion selbst beeinflussen können. Ersetzen wir Zeile 11 also mit folgendem:

$('#clickMe').click($.proxy(obj.clickHandler, obj));

Nun zeigt das this wärend unserem clickHandler-Aufruf wieder auf das eigentliche Objekt und eine Alert-Box mit dem Inhalt „Hello“ erscheint. Möchte man nun trotzdem auf das auslösende Element für das Event zugreifen, schafft

var calledBy = event.target;
// beispiel
$(calledBy).text("Link geklickt!");

abhilfe! Natürlich kann man auch seine eigene Proxy-Funktion mit direct-calling bauen, was aber den Code schon deutlich unübersichtlicher macht, allerdings viel mehr Flexibilität bietet (wieder Bezug auf 1. Beispielcode):

$('#clickMe').click((function(o) {
	return function(event) {
		o.clickHandler(event);
	};
})(obj));

Es lohnt sich also immer auch mal unter „Misc“ bzw. „Utilities“ in der Dokumentation einer Library nachsehen 😉

 

HTML5 WebSocket

HTML5 unterstützt jetzt das Aufbauen von „WebSocket“-Verbindungen. Eine WebSocket Verbindung basiert ähnlich wie HTTP auf dem TCP Protokoll, baut aber eine „dauerhafte“ Verbindung auf. Die Vorteile gegenüber HTTP ist, dass eine Aktion des Servers keine vorhergehende Anfrage des Clients mehr erfordert, wodurch der Server neue Informationen fasst in Echtzeit ausliefern kann. Dadurch kann man eine Menge Traffic und viele unnötige HTTP-Requests sparen, die etwa ein AJAX-JavaScript sonst schicken würde um neue Informationen zu erhalten.
Technisch gesehen baut bei einer WebSocket-Verbindung der Browser mit dem Server eine Verbindung auf, die nach einen Handshake dann bestehen bleibt und über die dann Binär- und Textdaten geschickt werden können.

Leider hat das Protokoll im Handshake einige Sicherheitsprobleme (gehabt?) und sich deshalb ständig verändert. Dieser Artikel bezieht sich also auf die Aktuellste Version (17). Diese wird derzeit von der aktuellsten Chrome Version und anderen Webkit Browsern unterstützt, in Firefox und Opera ist WebSocket wegen der Sicherheitsprobleme derzeit deaktiviert.

Um HTML5 WebSocket zu verwenden ist zunächst ein WebSocket-Server nötig. Ich habe hier nach einer PHP-Variante gesucht und habe dann nach einer Weile folgenden gefunden: php-websocket. Diesen sollte man sich laden und dann kann man ihn per

php -q php-websocket/server/server.php

starten. Jetzt ist ein WebSocket-Server auf „localhost:8000“ zu erreichen. Dort sind derzeit zwei Module geladen, einmal das Modul „echo“ und einmal das Modul „chat“. (Ich werde mich hier nicht mit der Entwicklung eines WebSocket-Server weiter befassen) Das „echo“ Modul nimmt einfach Anfragen entgegen und schickt diese dann an alle Verbundenen Sockets weiter.

Nun kümmern wir uns um die HTML5-Seite von WebSocket, dem User-Frontend. Dafür bauen wir uns erstmal eine einfache HTML5 Seite:





HTML5 WebSocket Test





// hier wird dann das WebSocket-Script reinkommen, siehe unten




Log

Chatbox

Say something

Für die einfache Verwendung des WebSockets habe ich mir eine kleine Wrapper-Library (websocketlib.js) programmiert:

/**
 * Small wrapper library for HTML5 WebSocket
 * 
 * @author Alexander Thiemann 
 * @version 1.0
 * 
 * @param string WebSocket host, eg. ws://localhost:8010/echo
 * @param function Function to be called when socket is connected
 * @param function Function to be called when socket recieves data, takes 1 argument message
 * @param function Function to be called for logging purposes, takes 1 argument log-message
 * 
 * @see http://blog.agrafix.net/2011/11/html5-websocket/
 */
var WebSocketLib = {
	init: function(host, onConnect, onRecv, onLog) {
		this.socket = new WebSocket(host);
		
		this.onLog = onLog;
		this.onRecv = onRecv;
		this.onConnect = onConnect;
		
		this.onLog('Initializing Socket. State: ' + this.socket.readyState);
		
		this.socket.onopen = this._onOpen;
		this.socket.onmessage = this._onMessage;
		this.socket.onclose = this._onClose;
	},
	
	send: function(message) {
		WebSocketLib.socket.send(message);
	},
	
	_onClose: function() {
		WebSocketLib.onLog('Socket Closed.');
	},
	
	_onMessage: function(msg) {
		WebSocketLib.onLog('Recieved new Message from Server: ' + msg.data);
		WebSocketLib.onRecv(msg.data);	
	},
	
	_onOpen: function() {
		WebSocketLib.onLog('Socket is Ready. State: ' + WebSocketLib.socket.readyState);
		
		if (WebSocketLib.socket.readyState == 1) {
			WebSocketLib.onConnect();
		}	
	}
}

Ich denke dort ist alles relativ selbsterklärend. Das WebSocket ist wirklich sehr leicht zu verwenden! Um diese Wrapper-Library zu verwenden, bauen wir in obiges HTML-Grundgerüst bei „// hier wird dann das WebSocket-Script reinkommen, siehe unten“ folgendes ein:

$(document).ready(function() {
	WebSocketLib.init('ws://localhost:8010/echo', 
	function() {
		sockReady();
	},
	function (t) {
		$('#chatbox').append($("

").text(t)); }, function (t) { $('#logbox').append($("

").text(t)); }); function sockReady() { $('#chat').submit(function(e) { e.preventDefault(); var input = $('input[name="message"]'); WebSocketLib.send(input.val()); input.val(""); }); } });

Zuerst warten wir, bis unser HTML-Dokument im Browser aufgebaut ist. Dann geben wir in Zeile 2 an, wo unser WebSocket-Server läuft. Die Funktion in Zeile 4 wird aufgerufen, sobald das WebSocket verbunden ist. Die Funktion Zeile 6-8 wird aufgerufen, wenn eine Nachricht zurückgegeben wird. Die Funktion Zeile 9-11 wird bei Log-Ereignissen (Hauptsächlich zum Debuggen) aufgerufen. Mit WebSocketLib.send(„Nachricht“) in Zeile 19 schicken wir eine Nachricht an den Server.

Wenn nun alles vorbereitet ist, der WebSocket-Server gestartet ist (siehe oben), können wir einem Browser, der WebSocket unterstützt (siehe oben), unsere HTML-Seite aufrufen. Die Log-Ausgabe im HTML-Fenster sieht wie folgt aus:

Initializing Socket. State: 0
Socket is Ready. State: 1

Im Consolenfenster des PHP-Server sehen wir:

2011-11-25 11:26:47 [info] [client 127.0.0.1:30601] Connected
2011-11-25 11:26:47 [info] [client 127.0.0.1:30601] Performing handshake
2011-11-25 11:26:47 [info] [client 127.0.0.1:30601] Handshake sent

Die Verbindung ist nun also aufgebaut. Jetzt sollten wir den „echo“-Server testen. Wir tippen etwas in das Textfeld und schicken es ab. Der Log sagt:

Recieved new Message from Server: test

Und unter „Chat“ steht jetzt „test“. Hat soweit also funktioniert! Jetzt können wir ein neues Browserfenster aufmachen, wieder den Verbindungsvorgang abwarten und dann dort eine Nachricht verschicken. Diese erscheint dann in allen verbundenen Client-Fenstern.

Eigentlich ziemlich einfach zu verwenden, und es öffnet viele neue Möglichkeiten vorallem in Kombination mit Canvas für HTML5-Multiplayer-Games… hoffen wir also, dass es bald von allen Browser voll unterstützt wird! 🙂

 

Sprechblasen mit HTML5, Canvas und JavaScript

Mit HTML5, Canvas und JavaScript kann man mittlerweile eine Menge schöner Grafiken direkt im Browser rendern. Da ich neulich eine flexible Sprechblase benötigt habe, hier der Code dafür:

/**
 * Draw a speech bubble on an HTML5-Canvas
 *
 * @author Alexander Thiemann 
 * @license CC BY-SA 2.0
 * 
 * @param ctx Canvas 2d context
 * @param text Text for the speech bubble
 * @param x bottom left x-coordinate of speech bubble
 * @param y bottom left y-coordinate of speech bubble
 *
 */
function speechBubble(ctx, text, x, y) {
	var messure = ctx.measureText(text);
	
	var w = messure.width;
	var h = 20;
	
	ctx.beginPath();
	ctx.strokeStyle="black";
	ctx.lineWidth="1";
	ctx.fillStyle="rgba(255, 255, 255, 0.8)";
	
	ctx.moveTo(x, y);
	ctx.lineTo(x + (w*0.2), y);
	ctx.lineTo(x + (w*0.2), y+10);
	ctx.lineTo(x + (w*0.3), y);
	ctx.lineTo(x + w, y);
	
	ctx.quadraticCurveTo(x + (w*1.1), y, x + (w*1.1), y-(h*0.2)); // corner: right-bottom
	
	ctx.lineTo(x + (w*1.1), y-(h*0.8)); // right
	
	ctx.quadraticCurveTo(x + (w*1.1), y-h, x + w, y-h); // corner: right-top
	
	ctx.lineTo(x, y-h); // top
	
	ctx.quadraticCurveTo(x - (w*0.1), y-h, x - (w*0.1), y-(h*0.8)); // corner: left-top
	
	ctx.lineTo(x - (w*0.1), y-(h*0.2)); // left
	
	ctx.quadraticCurveTo(x - (w*0.1), y, x, y); // corner: left-bottom
	
	ctx.fill();
	ctx.stroke();
	ctx.closePath();
	
	ctx.textAlign = 'left';
	ctx.fillStyle = '#000';
	ctx.fillText(text, x, y-6);
}

Verwendung:
HTML


JavaScript

var ctx = document.getElementById("bubble").getContext('2d');
speechBubble(ctx, "Das hier ist ein Test!", 5, 40);

Das Resultat:
Realisiert mit HTML5, Canvas und JavaScript

Ziemlich schick, oder? 😉

 

AJAX und der „Vor“ und „Zurück“ Button

Kleiner Hinweis: Dieser Blogpost nimmt an, das jQuery verwendet wird 😉

Dank AJAX kann man eine Menge Traffic einsparen – allerdings hat AJAX leider ein paar Nachteile: Die Browsernavigation, also die „Vor“ und „Zurück“ Knöpfe des Browsers, funktionieren nicht mehr.
Hier mal ein Beispiel:

HTML:

Inhalt ändern
Hier ist Inhalt 1

JavaScript:

$("a").live("click", function (e) {
    e.preventDefault();
    
    $('#content').load(e.target.href, function() {
        // nothing here yet ;)
    });
});

Klickt man nun auf „Inhalt ändern“, erscheint der Inhalt von content.html im content-div. Wenn jetzt der Benutzer allerdings wieder „Hier ist Inhalt 1“ sehen möchte, kann er nicht den „Zurück“-Button seines Browsers verwenden.

Um dieses Problem zu lösen gibt es zwei Möglichkeiten:

  • Einen hash an die aktuelle URL anhängen, etwa so: /#!content.html
  • Das HTML5 window.history-Objekt bemühen

Da die zweite Methode leider (noch) nicht von jedem Browser unterstützt wird, aber die wesentlich hübschere Methode ist, habe ich mir ein Objekt geschrieben, was beides kann!

/**
 * navigatorHistory
 *
 * Fixes navigator history for ajax-based sites
 *
 * @author agrafix 
 */
var navigatorHistory = {
	/**
	 * Title of site
	 */
	siteTitle: '',
	
	/**
	 * function to load content, needs to be set before hook()-call
	 */
	loaderFunc: null,

	/**
	 * check if the browser supports pushState
	 */
	isSupported : function() {
		return (typeof (window.history.pushState) == 'function');
	},

	/**
	 * add url to history
	 */
	add : function(url) {

		if (this.isSupported()) {
			
			window.history
					.pushState(null, this.siteTitle, url);
		} else {
			window.location.hash = "#!" + url;
		}
	},

	/**
	 * listen for back-forward button events
	 */
	hook : function() {
		if (!this.loaderFunc) {
			alert("No loaderFunc defined!");
			return;
		}
	
		if (this.isSupported()) {
			// doesn't work with jquery, no idea why :O
			window.addEventListener("popstate", function(e) {
				navigatorHistory.loaderFunc(document.location.pathname, true);
			});
		} else {
			$(window).bind('hashchange', function() {
				var location = (window.location.hash).replace(/^#!/, '');
				navigatorHistory.loaderFunc(location, true);
			});
		}

	}
};

Die Benutzung in Verbindung mit dem obigen HTML-Schnipsel ist denkbar einfach:

// funktion zum laden der Seiten definieren
navigatorHistory.loaderFunc = function(url, nohistory) {
	if (!nohistory) {
		navigatorHistory.add(url);
	}
	
	$('#content').load(url);
};

// seiten titel
navigatorHistory.siteTitle = "Deine Seite";

// auf vor bzw. zurück-button klicks hören
navigatorHistory.hook();

// die eigentliche ajax-funktion
$("a").live("click", function (e) {
    e.preventDefault();
	
	navigatorHistory.loaderFunc(e.target.href);
});

Eigentlich relativ einfach einzubauen und es macht eine Seite deutlich benutzerfreundlicher!