HowTo: App für das Palm Pre erstellen

Leider warte ich noch auf mein Palm Pre, aber da Palm seit einiger Zeit ein SDK für webOS zur Verfügung stellt, hindert mich nichts dran, mich schon mal in der Entwicklung zu versuchen.

Dazu brauchen wir erstmal das Palm Pre SDK. Dabei wird gleich eine virtuelle Maschine per Java Virtual Box mit einem WebOS installiert. Ansonsten wird alles installiert, was ihr braucht, um eine WebOS App zu erstellen. Wenn ihr wollt könnt ihr nun mit jedem beliebigen Texteditor loslegen.
Ich habe mich für Eclipse mit der webOS-Erweiterung entschieden. Du kannst die App von hier aus direkt in der VM starten, was einfacher ist, als die Apps jedes mal per Command-Tool in entsprechende Formate zu konvertieren und in die VM zu portieren.
Wie ihr das ganze installiert, könnt ihr hier nachlesen.

Nun will ich euch zeigen, wir ihr eine App für das Palm Pre erstellt. Als Beispiel entwickeln wir ein simples TicTacToe für zwei Spieler. Inspiriert wurde ich durch eine OpenSource Variante von TicTacToe namens Tics’n’Tacs, welche aber für Anfänger doch recht oversized wirkt (enthält eine KI etc.)

Da webOS mit dem Javascript Framework „Prototype“ arbeitet, welches man natürlich auch lokal nutzen kann, habe ich beschlossen, die Spiellogik erstmal für den Browser zu erstellen, und diese dann fürs Palm zu portieren und zu erweitern.

Als erstes erstellen wir eine Klasse in Javascript (kann man da von Klasse sprechen?):

function Game() {
}

/*
 * Setzt das Spielfeld zurück *
*/
Game.prototype.reset = function() {
	this.gameRunning = true;
	this.grid = [[ 0, 0, 0], [0, 0, 0], [0,0,0]];
	this.fieldsLeft = 9;
}


/*
 * Click abfangen und entsprechend reagieren
 */
Game.prototype.handleFieldClick = function(event, x, y) {
    // Spiel läuft nicht mehr oder Feld bereits von jemanden belegt!
	if(!this.gameRunning || this.grid[x][y] != 0) {
		return;
	}
	
	// Feld für den aktuellen Spieler reservieren
	this.grid[x][y] = this.currentPlayer;	
	var id = "field" + x + y;
	$(id).addClassName("p" + this.currentPlayer);
	
	// Züge reduzieren
	this.fieldsLeft--;
	
	// Prüfen, ob er gewonnen hat oder alle Felder belegt!
	if(this.checkForWin()) {
		// Hat gewonnen, callback funktion aufrufen!
		this.endGameCallback(this.winner);
		return;
	}
	// Aktuellen Spieler ändern
	this.currentPlayer = (this.currentPlayer == 1) ? 2 : 1;		

	
}

/*
 * Dient als Zeiger für die Callback Funktion, wird aufgerufen, sobald das Spiel vorbei ist.
 */
Game.prototype.endGameCallback = null;
	
	
/*
 * Startet das Spiel und setzt die Eventhandler auf die Felder. 
 */
Game.prototype.start = function() {
	var id;
	this.reset();
	for(var i = 0; i < 3; i++) {
		for(var j = 0; j < 3; j++) {
			id = "field" + i + j;
			Event.observe($(id), "click", this.handleFieldClick.bindAsEventListener(this, i, j));			
		}	
	}

}	

/* 
 * Gibt die Debugmeldung für das Spielfeld aus
 */
Game.prototype.debug = function() {
	alert(this.grid[0][0] + " " + this.grid[0][1] + " " + this.grid[0][2] + "\n" +this.grid[1][0] + " " + this.grid[1][1] + " " + this.grid[1][2] + "\n" +this.grid[2][0] + " " + this.grid[2][1] + " " + this.grid[2][2] + "\n");
}

/*
 * gibt true zurück, wenn ein Spieler gewonnen hat oder Spiel zuende ist
 */
Game.prototype.checkForWin = function() {	
	if(this.checkWinDiagonal() || this.checkWinCol() || this.checkWinRow()) {
		this.gameRunning = false;
		return true;
	}
	
	if(this.fieldsLeft == 0) {
		this.winner = 3;
		return true;
	}
}

/* Prüft, ob Spieler diagonal eine Reihe hat */
Game.prototype.checkWinDiagonal = function() {
	if(	(
			(this.grid[0][0] == this.grid[1][1] &&
			this.grid[1][1] == this.grid[2][2]) ||	
			(this.grid[2][0] == this.grid[1][1] &&
			 this.grid[1][1] == this.grid[0][2]))
		&& this.grid[1][1] != 0) {
			this.winner = this.grid[1][1];			
			return true;
		}			
	return false;
}

/* prüft ob Spieler eine Spalte voll hat */
Game.prototype.checkWinCol = function() {
	for(var i = 0; i < 3; i++) {
		if(	this.grid[0][i] == this.grid[1][i] &&
			this.grid[1][i] == this.grid[2][i] &&
			this.grid[0][i] != 0) {
				this.winner = this.grid[0][i];				
				return true;
			}		
	}
	
	return false;	
}

/* prüft ob Spieler eine Reihe voll hat */
Game.prototype.checkWinRow = function() {
	for(var i = 0; i < 3; i++) {
		if(	this.grid[i][0] == this.grid[i][1] &&
			this.grid[i][1] == this.grid[i][2] &&
			this.grid[i][0] != 0) {				
				this.winner = this.grid[i][0];
				return true;
			}		
	}
	
	return false;	
}

// 0: game running, 1: player 1, 2:player 2, 3: draw
Game.prototype.winner = 0;
Game.prototype.fieldsLeft = 9;
Game.prototype.gameRunning = false;
Game.prototype.currentPlayer = 1;
Game.prototype.grid = null;

Das Spielfeld:

<html>
<head><title>Tic Tac Toe</title>
<script type="text/javascript" src="prototype.js" />
<script type="text/javascript" src="game.js" />
  <link href="style.css" rel="stylesheet" type="text/css"/>
</head>
<script type="text/javascript">

var game = null;
function startgame() {
	game = new Game();
	game.start();	
	game.endGameCallback = spielEnde;
}

function spielEnde(winner) {
	if(winner == 1 || winner == 2) {
		alert("Gewonnen hat Spieler " + winner);
	}
	else {
		alert("Keiner hat gewonnen!");
	}
	
	//game.debug();
}

</script>
<body>

<div id="spielfeld">
	<div class="row row1">
		<div id="field00" class="cell col1">1</div>
		<div id="field01" class="cell col2">2</div>
		<div id="field02" class="cell col3">3</div>
	</div>
	<div class="row row2">
		<div id="field10" class="cell col1">4</div>
		<div id="field11" class="cell col2">5</div>
		<div id="field12" class="cell col3">6</div>
	</div>
	<div class="row row3">
		<div id="field20" class="cell col1">7</div>
		<div id="field21" class="cell col2">8</div>
		<div id="field22" class="cell col3">9</div>
	</div>
</div>
<div style="clear:both;"></div>

<input type="button" onclick="startgame();" value="start game"/>


</body>
</html>

Per div-Container und CSS wird ein Spielfeld geformt. Nach einem Klick auf „start game“ wird die Klasse initialisiert und die Variabeln gesetzt (game.start()). Dort werden „OnClick“-Events auf die einzelnen Spielfelder gesetzt. Klickt nun ein Spieler auf ein Feld, wird geprüft, ob es noch frei ist und für den Spieler reserviert und durch hinzufügen der entsprechenden CSS-Klasse auch mit einer entsprechenden Grafik visuell markiert. Danach wird geprüft, ob das Spiel vorrüber ist (weil einer gewonnen hat oder kein Zug mehr möglich ist) und bei Bedarf die Funktion „endGameCallback()“ aufgerufen. Wenn ihr noch eine Runde spielen wollt, müsst ihr die Seite neuladen.

Damit ihr das nun nicht alles per Copy&Paste selbst machen müsst, habe ich euch alles in einer ZIP-Datei zusammen gepackt: tictactoe.zip

Nun erstellen wir daraus eine webOS Applikation. Dazu legen wir in Eclipse eine neue Mojo App an:

Nun legen wir eine neue Mojo-Scene an und nennen sie „game“:

Wir legen (der Übersicht halber) unter dem Ordner „app“ einen Ordner „model“ an und kopieren die game.js von oben dortrein.

Damit die „game.js“ später in der App bekannt ist, muss sie in der Datei „sources.json“ eingetragen werden. Erweitert sie in etwa so:

[
    {"source": "app/assistants/stage-assistant.js"},
    {
        "scenes": "game",
        "source": "app/assistants/game-assistant.js"
    },
    {
    	"source": "app/model/game.js"
    }
]

In der Datei „/app/views/game/game-scene.html“ füge ich nun das Spielfeld hinzu. Wichtig ist, dass ich hier keinen Header einbaue.

<div class="palm-page-header">
  <div class="palm-page-header-wrapper">
    <div class="icon app"></div>
    <div class="title">Tic Tac Toe</div>
  </div>
</div>
<div id="spielfeld">
	<div class="row row1">
		<div id="field00" class="cell col1">1</div>
		<div id="field01" class="cell col2">2</div>
		<div id="field02" class="cell col3">3</div>
	</div>
	<div class="row row2">
		<div id="field10" class="cell col1">4</div>
		<div id="field11" class="cell col2">5</div>
		<div id="field12" class="cell col3">6</div>
	</div>
	<div class="row row3">
		<div id="field20" class="cell col1">7</div>
		<div id="field21" class="cell col2">8</div>
		<div id="field22" class="cell col3">9</div>
	</div>
</div>

Die beiden Bilder 1.png und 2.png kopiere ich in den Ordner „/images“. Den Inhalt der style.css kopiere ich in die Datei „/resources/tictactoe.css“ und passe die Pfade für die beiden Bilder an.

Nun müssen wir die Spiellogik der game.js in die App einbauen. Das ist ähnlich, wie in der HTML-Datei am Anfang des Beitrags. Dazu bearbeite ich die „/app/assistants/game-assistant.js“ und erweitern die vorhandenen Funktionen wie hier (bzw. fügen sie hinzu):

var game = null;
GameAssistant.prototype.setup = function() {
	this.game = new Game();
	this.game.start();
	this.game.callback = this.spielEnde;
}

GameAssistant.prototype.spielEnde = function(winner) {
	
	var endText;
	if(winner == 3) {
		endText = "Das Spiel endete unentschieden.";
	} else {
		endText = "Gewonnen hat Spieler " + winner;
	}
	Mojo.Controller.errorDialog(endText);
}

Unterschiedlich ist hier nur die Callback-Funktion. Sie ruft die Funktion „errorDialog()“ auf. Dies ist ein webOS Dialog. Es gibt noch andere Dialoge, aber halten wir es mal einfach. Das Ergebnis seht ihr später.

Nun müssen wir der App auch sagen, dass es die Szene „game“ starten soll, dies geschieht in der „stage-assistant.js“:

StageAssistant.prototype.setup = function() {

this.controller.pushScene("game");
}

Nun starten wir die App (Emulator muss schon laufen), in dem wir aufs Projekt rechtsklicken und „Run“ -> „Mojo Application“ auswählen. Nach einem kurzen Moment startet die App im Emulator. Das Ergebnis:

Und zu guter letzt noch das ganze Eclipseprojekt als Download: tictactoe-eclipse.zip

2 Gedanken zu „HowTo: App für das Palm Pre erstellen“

  1. Als nächstes werde ich mir mal die Debugmöglichkeiten ansehen. Wie man auf den letzten Screenshots sehen kann, liegen zwischen ihnen bald eine Stunde. Leider funktionierte die Callback-Funktion nicht so wie anfangs gewünscht, so das ich mit try-and-error rumprobiert habe.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.