Browsergame - Ressourcenanzeige in PHP programmieren

Eli-

Mitglied
Hi,

es mich interessiert schon lange, wie Browsergames funktionieren. Also habe ich beschlossen, mal ein paar Dinge nachzuprogrammieren. Ich will jetzt kein richtiges, vollständiges Browsergame programmieren, sondern nur mal ein paar Teile davon. Einfach um zu verstehen, wie das mit dem Gebäudeaufrüsten, den Rohstoffanzeigen etc. funktioniert.

Ich bin halt noch kein richtiger Profi in PHP. Beispielswesie fehlen mir alle Kenntnisse zu OOP (was ich aber jetzt lernen will). Und mir fehlen auch "Datenbank-Optimierungs-Kenntnisse". Aber mein Motto ist: learning-by-doing :)

Also hab ich mich mal drangesetzt und überlegt, wie man (in meinem Falle jetzt) diese Ressourcenanzeige da macht. Also wie man es schafft, das die immer steigt und steigt. Meine Idee ist, dass man in einer Datenbanktabelle die Anzahl der Rohstoffe und eine Zeit speichert. Dann kann man bei einem Reload die aktuelle Zeit mit der Zeit in der Datenbank subtrahieren und damit dann berechnen, wie viel Rohstoffe dazugekommen sind. Und dann die neuen Werte wieder in die Datenbank eintragen. Ob das jetzt die gängiste Methode ist, weiß ich nicht. Aber ich hab das Ganze mal versucht:

PHP:
<?php
// Verbindung zur Datenbank herstellen
$db = @new MySQLi('localhost', 'root', '', 'browsergameTest');
if (mySQLi_connect_errno()) {
    die('<fieldset>in index.php: Konnte keine Verbindung zu Datenbank aufbauen, MySQL meldete: '.mysqli_connect_error().'</fieldset>');
}

// Funktion zur Berechnung des neuen Ressourcenbestands
// Parameter sind name, alter Ressbestand und die alte Zeit aus der Datenbank
// Zusatzfunktion(geplant): Neuer Parameter: Steigfaktor
// 		-> Soll bestimmen, um wieviel die ress pro Stunde steigen sollen
// 		-> Ermöglicht das Ausbauen der Ressgebäude
function ressCalc($name,$ressOld,$timeOld) {
	
	// Aktuelle Zeit bestimmen
	$timeCurr = time();
	// Zeitdifferenz bilden	
	$timeDiff = $timeCurr - $timeOld;
	// Sekunden in Stunden umrechnen
	$timeDiff = $timeDiff / 3600;
	// Anzahl der Ressourcen, die Pro Stunde gutgeschrieben werden sollen
	$ressAdd = $timeDiff * 10000;
	// Alter Ressourcenbestand mit der hinzugekommenen Menge addieren
	$ressNew = $ressOld + $ressAdd;
	
	// Rückgabe per Array: Der Neue Bestand und die vorher bestimmte Zeit.
	return array ($ressNew, $timeCurr);
	
}

// id, name, anzahl und zeit von Tabelle "ress" nehmen, bei id=1
$sqlQuery = $db->query("SELECT id,name,anzahl,zeit FROM `ress` WHERE `id`=1");
$value = $sqlQuery->fetch_assoc();
	
	// Rückgabewerte der Funktion ressCalc in der Variable returns speichern
	$returns = ressCalc($value['name'],$value['anzahl'],$value['zeit']);
	// Den ersten Wert im Array, den Ressourcenbestand, in $ressNew speichern
	$ressNew = $returns[0];
	// Die Zeit in $timeCurr speichern
	$timeCurr = $returns[1];
	
	// Neue Werte in Datenbank eintragen
	$sql= "
	UPDATE	ress 
	SET 	anzahl = $ressNew,
	    	zeit = $timeCurr
	WHERE 	id = 1
	";
	$insert = $db->query($sql);
	
	// Aktuellen Ressourcenbestand ohne Komma ausgeben
	echo "<b>".$value['name']."</b>: ";
	echo (int) $ressNew;

?>

Ich hoffe, der Code ist nicht zu chaotisch. Ich hab immer noch Probleme damit, geeignete Variablennamen zu finden.

Meine Frage jetzt: Ist der Code grundsätzlich okay (abgesehen davon, dass er vllt umständlich programmiert ist oder so), oder wird das komplett anders geregelt?


Und noch was: Man hat ja nicht nur eine Ressource in einem Spiel, sondern mehrere. Schreibt man die dann in dieselbe Datenbanktabelle (z.B. eben in "ress")? Weil dann könnte man ja hier:
PHP:
$sqlQuery = $db->query("SELECT id,name,anzahl,zeit FROM `ress` WHERE `id`=1");
das
PHP:
WHERE id=1
weglassen und per while-Schleife alle Ressourcen durch die Funktion jagen. Dann müsste man halt nur den Steigfaktor (in den Kommentaren vom Code erklärt) pro Ressource individuell einstellen.

Weil dieser Steigfaktor kann ja von z.B. ner Gebäudestufe abhängen. die Frage ist, wie man das dann macht. Sollte man dazu auch eine seperate Funktion erstellen, die dann den Steigfaktor berechnet, eben anhand von Gebäudestufen? Wenn ja, wo würde dann diese Funktion ausgeführt werden? Immer beim Reload der Seite, vor der berechnung der Ressoucen?
Danke schon mal für eure Hilfe.

Viele Grüße,
Dennis
 
Also vom Prinzip her würde ich sagen, dass das so umgesetzt wird. Du musst natürlich nur aufpassen, wenn sich der Steigungsfaktor ändert oder absolute Beträge dazukommen oder abgezogen werden (Kauf, Verkauf, Plünderungen, Beute).

Zu dem das WHERE weglassen: Hast du nur einen Benutzer oder legst du für jeden User (Benutzer) ne eigene Ressourcen-Tabelle an? :p Also eine WHERE-Bedingung brauchst du auf jedenfall, und zwar in der art WHERE user_id = $userid ;)

Zum Chaos: Fang doch an, das Projekt objektorientiert zu gestalten. Dadurch wird es übersichtlicher, wenn du es richtig angehst (Für diesen Codeabschnitt könntest du über die Klassen Datenbank und Ressource nachdenken...)
 
Danke für die schnelle Antwort.

Stimmt, das mit den absoluten Beträgen habe ich vergessen.
Sollte man das dann nach der "normalen" Ressourcenberechnung machen? Also das erst die Ressourcen erhöht werden und dann wird etwas abgezogen oder hinzugefügt?

Mit dem WHERE: Das ist eben die große Frage. Ich wüßte gar nicht, wie man sowas Datenbankmäßig organisiert. Das muss ich mich noch mal mit beschäftigen. Aber meine Idee wäre:

Eine Tabelle für user, mit deren id, name und passwort
Dann eine Tabelle für Ressourcen mit userid, ressname, anzahl und zeit.

Dann kann man pro User eine neue Zeile anlegen. Würde das so gehen? Wenn ja, dann könnte das doch auch mit Gebäuden etc. funktionieren. Dann müsste man nur in einer Tabelle allen Gebäuden eine oder mehrere Spalten zuweisen.

Oder sollte man für jedes Gebäude/jede Ressource einzeln eine Tabelle anlegen?

Ja, objektorientiert wäre gut. Nur kann ich das leider nicht^^. In meinen PHP Büchern war das nicht dabei. Aber ich hab mir ein neues bestellt, was das hoffentlich beinhaltet. Dann kann ich das ja mal versuchen.

Danke aber nochmal :D
 
Ja, so würde ich zumindest vorgehen.

Tabellen:
-user(id, name, ...)
-ressources(id, user_id, name(type=enum?), amount, factor, [...])
-buildings(id, user_id, name, level, [...])

Du musst niemals Tabellen dynamisch generieren! Und auch kein Spalten! Tabellen wachsen nach unten. Mach dir also vorher Gedanken über ein vernünftiges Datenbank design, sonst wirst du es später bereuen.

Thema OOP: http://www.peterkropff.de/site/php/oop.htm
 
Hab mal was versuch mit Klassen zu machen. Zwar weiß ich nicht, wozu man die Klasse Datenbank braucht, aber ich hab mal ne Klasse Ressource erstellt.

index.php
PHP:
<?php

// Verbindung zur Datenbank herstellen
$db = @new MySQLi('localhost', 'root', '', 'browsergameTest');
if (mySQLi_connect_errno()) {
    die('<fieldset>in index.php: Konnte keine Verbindung zu Datenbank aufbauen, MySQL meldete: '.mysqli_connect_error().'</fieldset>');
}

include_once("class.inc.php");



$sqlQuery = $db->query("SELECT id,name,anzahl,zeit FROM `ress` WHERE `id`=1");
$value = $sqlQuery->fetch_assoc();
	
$eisen = new Ressource($value['name'], $value['anzahl'], $value['zeit']);
$eisenOutput = $eisen->ressCalc();
echo (int) $eisenOutput[0];

?>

class.inc.php:
PHP:
<?php


class Ressource
{
	private $name;
	private $amount;
	private $timeOld;
	
	function __construct($name, $amount, $timeOld)
	{
		$this->name = $name;
		$this->amount = $amount;
		$this->timeOld = $timeOld;
	}
	
	function ressCalc()
	{
		$timeCurr = time();
		
		$timeDiff = $timeCurr - $this->timeOld;
		$timeDiff = $timeDiff / 3600;
		$amountAdd = $timeDiff * 10000;
		
		$amountNew = $this->amount + $amountAdd;
		
		$sql= "
		UPDATE	ress 
		SET 	anzahl = $amountNew,
				zeit = $timeCurr
		WHERE 	id = 1
		";
		$sqlquery = mysql_query($sql);
		
		return array ($amountNew, $timeCurr);
		
		
	}
}




?>

Leider funktioniert dabei das Updaten der Datenbanktabelle nicht. Und ich find einfach nicht raus, warum. Es kommt auch keine Fehlermeldung oder so. Zwar aktualisiert er die Zahl immer, aber er verändert nichts an der Datenbank.
 
In der Objektorientierten Welt ist ALLES ein Objekt, also auch die Datenbank. Natürlich wissen wir, dass eine Datenbank eigentlich kein PHP-Objekt ist, sondern eine Datenbank. Um hier trotzdem mit Objekten arbeiten zu können, erstellt man ein Datenbankobjekt (eine Datenbankklasse), die sozusagen die Datenbank in der PHP-Anwendung darstellt und sich auch so verhält. (Abstraktion)

Man spricht vom sogenannten DBAL, Database Abstraction Layer. Ein Vorteil davon ist zB., dass man die datenbank eifnach austauschen kann (MYSQL <-> PostgreSQL) ohne etwas am gesamten Code verändern zu müssen. Man muss dann eben nur dieses Objekt austauschen. [Mit am ganzen Code was ändern meine ich die Queries, denn es könnte ja sein, dass was bei MySQL "SELECT *" ist, bei PostrgeSQL "SELECT +"...)

Deine klasse kan nicht funktionieren. Die Datenbankverbindung existiert in der klasse selber nicht und des Weiteren verwendest du einmal mysql_*, das andere mal mysqli_* ;)
 
Schreib mal die ressCalc ein bisschen um:

PHP:
    function ressCalc()
    {
        $timeCurr = time();
        
        $timeDiff = $timeCurr - $this->timeOld;
        $timeDiff = $timeDiff / 3600;
        $amountAdd = $timeDiff * 10000;
        
        $amountNew = $this->amount + $amountAdd;
        
        $sql= "
        UPDATE    ress 
        SET     anzahl = $amountNew,
                zeit = $timeCurr
        WHERE     id = 1
        ";
        if( ! ($sqlquery = mysql_query($sql) )
        {
            throw new Exception( mysql_error() . "\n<pre>$sql</pre>" );
        }
        if( ! mysql_affected_rows() )
        {
            throw new Exception( "Update nicht durchgeführt!\n<pre>$sql</pre>" );
        }
        
        return array ($amountNew, $timeCurr);
    }

Und wenn du ohnehin schon mit Klassen hantierst, solltest du dir vllt. MySQLi oder besser noch PDO ansehen. Dann gehen damit nämlich so coole Dinge:

PHP:
<?php
/* Mapper-Klasse */
class Ressource
{
  public $id;
  public $name;
  public $anzahl;
  public $zeit;

  /*** GETTER, SETTER, DAO-Logik .... ***/
}

/* Datenbank-Verbindung (Datasource) */
$pdo = new PDO('mysql:host=xxx;port=xxx;dbname=xxx', 'username', 'password');
/* Exception-Handling und weitere Einstellungen vornehmen */
$pdo->setValue(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

/* Prepared Statement vorbereiten */
$statement = $pdo->prepare("SELECT id,name,anzahl,zeit FROM `ress` WHERE `id`= ?");
/* ID-Platzhalter durch Wert ersetzen (SQL-Injection verhindern) */
$statement->bindValue(1, 1);
/* Query ausführen */
$statement->execute();

/* Jetzt Ergebnis abholen und direkt in ein Objekt der Klasse "Ressource" mappen */
$ressource = $statement->fetch(PDO::FETCH_CLASS, 'Ressource');

/* Zur Kontrolle */
var_dump($ressource);

$ressource->ressCalc();

Dann könntest du noch etliche andere Verbesserungen einbauen ;-) Aber versuch erst mal das zu verstehen und scheu dich nicht, Fragen zu stellen :-)

EDIT: Übrigens ist es ratsam, in der Entwicklung mit Fehler-Anzeige zu arbeiten:

PHP:
// Schreib mich in die ersten Zeilen des Haupt-Scripts
error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 1);
 
Zuletzt bearbeitet:
Ach du Schande ist das kompliziert.

Mit der Datenbankklasse: Okay, aber ich hab (noch) nicht vor, die Datenbank auszutauschen. Wüsste auch gar nicht, wie das gehen sollte.

@saftmeister:
Hab den Code um das erweitert und nur das query als objektorientiert gemacht (glaub ich). Jetzt kommt folgende Fehlermeldung:

Fatal error: Uncaught exception 'Exception' with message 'Update nicht durchgeführt! <pre>UPDATE ress SET eisen = 43482.444444444, zeit = 1366546557 WHERE user_id = 1</pre>' in C:\xampp\htdocs\browsergame\ress\class.inc.php:37 Stack trace: #0 C:\xampp\htdocs\browsergame\ress\index2.php(27): Ressource->ressCalc() #1 {main} thrown in C:\xampp\htdocs\browsergame\ress\class.inc.php on line 37

Weiter unten ist der Code.

Das mit dem PDO: Ich versteh irgendwie den Zusammenhang da nicht wirklich. Wozu ist das gut? Laut dem Link den du geschrieben hast, ist es eine Schnittstelle. Aber ich verstehe den Vorteil davon nicht ganz.

Und das mit dem, dass er alle Fehler anzeigt, hab ich jetzt mal eingefügt.

______________________________

Ich hab nur, bevor ich die neuen Antworten hier gelesen habe, den Code und die Datenbank etwas verändert, damit man mehrere Ressourcen haben kann:

Datenbankspalten:
id
user_id
eisen (mit der Anzahl als Wert)
kristall (mit der Anzahl als Wert)
factor1 (gibt an, wie stark die Ressourcen wachsen sollen)
factor2 (gibt an, wie stark die Ressourcen wachsen sollen)
zeit

Dann index2.php
PHP:
<?php
error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 1);

// Verbindung zur Datenbank herstellen
$db = @new MySQLi('localhost', 'root', '', 'browsergameTest');
if (mySQLi_connect_errno()) {
    die('<fieldset>in index.php: Konnte keine Verbindung zu Datenbank aufbauen, MySQL meldete: '.mysqli_connect_error().'</fieldset>');
}
include_once("class.inc.php");

$sqlQuery = $db->query("SELECT id,eisen,kristall,zeit,factor1,factor2 FROM `ress` WHERE user_id=1");
$value = $sqlQuery->fetch_assoc();
	
	
$eisen = new Ressource("eisen", $value['eisen'], $value['zeit'], $value['factor1']);
$kristall = new Ressource("kristall", $value['kristall'], $value['zeit'], $value['factor2']);

$eisenOutput = $eisen->ressCalc();
$kristallOutput = $kristall->ressCalc();

echo (int) $eisenOutput[0];
echo "<br><br>";
echo (int) $kristallOutput[0];
?>

class.inc.php
PHP:
<?php


class Ressource
{
	private $name;
	private $amount;
	private $timeOld;
	private $factor;
	
	function __construct($name, $amount, $timeOld, $factor)
	{
		$this->name = $name;
		$this->amount = $amount;
		$this->timeOld = $timeOld;
		$this->factor = $factor;
	}
	
	function ressCalc()
	{
		$db = @new MySQLi('localhost', 'root', '', 'browsergameTest');
		$timeCurr = time();
		
		$timeDiff = $timeCurr - $this->timeOld;
		$timeDiff = $timeDiff / 3600;
		$amountAdd = $timeDiff * $this->factor;
		
		$amountNew = $this->amount + $amountAdd;
		
		$sql = "UPDATE ress SET $this->name = $amountNew, zeit = $timeCurr WHERE user_id = 1";
		if( ! ($sqlQuery = $db->query($sql)))
        {
            throw new Exception( mysql_error() . "\n<pre>$sql</pre>" );
        }
        if( ! mysql_affected_rows() )
        {
            throw new Exception( "Update nicht durchgeführt!\n<pre>$sql</pre>" );
        }
		
		
		
		return array ($amountNew, $timeCurr);
		
		
	}
}



?>
Wie oben schon geschrieben kommt halt jetzt ne Fehlermeldung, wegen diesen "new Exception".

Was macht eigentlich dieses throw new Exception? Es sieht irgendwie aus, als würde das nur etwas ausgeben, so wie echo.
 
Das mit dem PDO: Ich versteh irgendwie den Zusammenhang da nicht wirklich. Wozu ist das gut? Laut dem Link den du geschrieben hast, ist es eine Schnittstelle. Aber ich verstehe den Vorteil davon nicht ganz.

Wozu sind die mysql_*-Funktionen gut? Richtig, um die Kommunikation mit der Datenbank zu ermöglichen. Zu einer MySQL Datenbank hat man dazu in PHP 3 Möglichkeiten:

1.) mysq_* Extension: prozedural, demnächst veraltet
2.)mysqli_* Extension: prozedural & objektorientiert, Beschränkung auf MySQLI
3.)PDO: komplett objektorientiert, prepared Statement, mehrere datenbanken unterstützt

Zu den weiteren fragen: Ich empfehle das oben verlinkten Tutorial durchzuarbeiten, da steht auch irgendwann mal was zur Fehlerbehandlung. Also im Groben ist die "Exception" auch eine Klasse (Objekt), die immer dann aufgerufen wird, wenn man throw new Exception() schreibt. Optional kann man beim instantiieren 2 Paramter (Fehlertext, Fehlercode) übergeben.
 
Zuletzt bearbeitet:
Ich habe mal saftmeisters Code zur hand genommen und die Klasse komplett von Datenbankkram bereinigt. (Eine Klasse = eine Zuständigkeit)

Mit der Klasse kannst du dann auch weitere berechnungen durchführen bzw auch absolute Beträge abziehen und hinzufügen. Am Ende musst du das Ressource-Objekt nur wieder serialisiern (also wieder in die datenbank schreiben).

PHP:
<?php
/**
 * Ressource
 */
class Ressource
{

	public $id;
	public $name;
	public $amount;
	public $time;
	public $factor;

	/**
	 * Constrcutor
	 */
	public function __construct()
	{
		$this->calculate();
	}

	/**
	 * Getter
	 * @return int Amount of ressource
	 */
	public function getAmount()
	{
		return $this->amount;
	}

	/**
	 * Setter
	 * @param int $amount Amount of ressource
	 */
	public function setAmount($amount)
	{
		$this->amount = $amount;
	}

	/**
	 * Add a specific amount
	 * @param int $amount Amount of ressource
	 */
	public function addAmount($amount)
	{
		$this->amount += $amount;
	}

	/**
	 * Reduce ressource by specific amount
	 * @param  int $amount Amount of ressource
	 */
	public function reduceAmount($amount)
	{
		if ($this->getAmount() >= $amount) {
			$this->amount -= $amount;
		}
	}

	/**
	 * Internally used to calculate amount
	 * @return int Amount of ressource
	 */
	protected function calculate()
	{
		$timeCurr = time();
        
        $timeDiff = $timeCurr - $this->time;
        $timeDiff = $timeDiff / 3600;
        $amountAdd = $timeDiff * $this->factor;

        $this->addAmount($amountAdd);
	}
}

PHP:
<?php

require 'Ressource.php'

/* Datenbank-Verbindung (Datasource) */
$pdo = new PDO('mysql:host=xxx;port=xxx;dbname=xxx', 'username', 'password');

/* Exception-Handling und weitere Einstellungen vornehmen */
$pdo->setValue(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

/* Prepared Statement vorbereiten */
$statement = $pdo->prepare("SELECT id,name,anzahl,zeit FROM `ress` WHERE `id`= ?");

/* ID-Platzhalter durch Wert ersetzen (SQL-Injection verhindern) */
$statement->bindValue(1, 1);

/* Query ausführen */
$statement->execute();

/* Jetzt Ergebnis abholen und direkt in ein Objekt der Klasse "Ressource" mappen */
$ressource = $statement->fetch(PDO::FETCH_CLASS, 'Ressource');
var_dump($ressource->getAmount());

/*
@TODO: UPDATE
 */
 
Zurück