JeuWeb - Crée ton jeu par navigateur
Image dynamique d'une carte avec les domaines connus d'une région - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51)
+--- Sujet : Image dynamique d'une carte avec les domaines connus d'une région (/showthread.php?tid=401)



Image dynamique d'une carte avec les domaines connus d'une région - Raoull - 05-11-2006

Mise à jour le 12/11/06 : modif pour avoir toujours la dernière carte générée à l'écran et pas une vielle qui se trouverai dans le cache du navigateur. Pour ca on nomme chaque image de carte avec le 'time' du passage du cron, afin d'avoir des noms d'image uniques à chaque mise à jour. Et bien sur on vide le repertoire des cartes avant à chaque fois pour pas accumuler 50.000 images inutiles.

------

Le but de ce script est de créer un fichier, qui appelé par un cron, générera toutes les cartes de l'univers de jeu, avec, dans le cas précis présenté, les 'domaines' découverts et les 'domaines' inconnus', le relief et les routes pour chacun des domaines connus, pour chaque région.

Le cahier des charges :

Pour avoir un script le plus léger et le plus rapide possible (afin de ne pas avoir un timeout du cron qui ne générerait pas toutes les cartes des régions) on ne va chercher dans la base de données les infos des régions pour lesquelles on est sur qu'au moins un domaine a été découvert.
Les autres régions, les cartes donc vides, seront évitées (bien qu'une carte vide soit généré).

Voici déjà un aperçu du résultat, la carte générée pour 1 région :
La région de l'Agora fait 15*15 soit 255 domaines.

[Image: cartepl8.png]

L'univers :

Dans le cadre de ce script, l'univers de jeu est divisé en pays/région/domaine. Cela aurait pu être galaxie/systeme/planete ou n'importe quoi d'autre...

Pré-requis dans l'exemple précis de ce tuto :

Les pays et les régions sont définis à la base par le créateur du jeu, mais les domaines sont générées automtiquement lorsque qu'un joueur y passe ou l'aperçoit (domaines qui s'affichent autour du domaine où se trouve le joueur, sur la page situation, qui sont donc générés mais pas marqués comme 'connu' tant qu'un joueur n'y passe pas vraiment).

nous, on veut donc générer les cartes de toutes les régions, avec seulement les domaines CONNUS, et marqués comme tel dans la base.

Voici la structure des bases de données (simplifiées pour ce tuto) dont on a besoin :

Base "xp_conf" :
Base contenant les infos de configuration générale du jeu. Elle contient des données clef/valeur dans lesquelles on peut stocker ce que l'on veut.
Ici on va y mettre l'heure du dernier passage du cron, pour informer par exemple les joueurs de la dernière mise à jour.

Code :
--
-- Structure de la table `xp_conf`
--

CREATE TABLE xp_conf (
  id int(11) unsigned NOT NULL auto_increment,
  clef varchar(30) collate latin1_general_ci NOT NULL,
  valeur varchar(255) collate latin1_general_ci NOT NULL,
)

Base "xp_region" :
Cette base contient les données pour chaque région :
- id : vous savez ce que c'est
- nom : le nom de la région...
- id_pays : l'id du pays dont cette région dépends (ici on ne se servira pas de cette info puisqu'on génère les cartes pour toutes les régions, peu importe le pays.
- max_x : la largeur de la région, soit le nombre de domaine pour la largeur
- max_y : la hauteur de la région, soit le nombre de domaine pour la hauteur

Code :
--
-- Structure de la table `xp_region`
--

CREATE TABLE `xp_region` (
  `id` int(11) unsigned NOT NULL auto_increment,
  `nom` varchar(30) collate latin1_general_ci NOT NULL,
  `id_pays` int(11) unsigned NOT NULL,
  `max_x` tinyint(3) unsigned NOT NULL,
  `max_y` tinyint(3) unsigned NOT NULL,
)

Base "xp_dom" :
Cette base contient les données pour chaque domaine :
- id : vous savez ce que c'est
- p : id du pays
- r : id de la région
- x : position X de ce domaine dans la région mère
- y : position X de ce domaine dans la région mère
- relief : type de relief de ce domaine
- route : type de route de ce domaine
- connu : si ce domaine est connu (1 si le domaine a été visité au moins une fois, 0 si il a juste été apercu)

Code :
--
-- Structure de la table `xp_dom`
--

CREATE TABLE `xp_dom` (
  `id` int(11) unsigned NOT NULL auto_increment,
  `p` int(11) unsigned NOT NULL,
  `r` int(11) unsigned NOT NULL,
  `x` tinyint(3) unsigned NOT NULL,
  `y` tinyint(3) unsigned NOT NULL,
  `relief` tinyint(2) unsigned NOT NULL,
  `route` tinyint(1) unsigned NOT NULL,
  `connu` tinyint(1) unsigned NOT NULL,
)

Images :

Voici un exemple des graphismes que nous allons utiliser, elles font toutes 100px * 100px.

Le nom des images n'est pas stocké dans la base (inutile à mon gout) mais dans un repertoire, nommés par numéro. Dans la base, pour la valeur de relief 5 on saaura qu'il faudra afficher l'image $relief.'.gif'

PS : merci de ne pas se moquer de mes images... C'est du provisoir :$  

Le relief :

1.gif : desert : [Image: 1.gif]
2.gif : plaine : [Image: 2.gif]
3.gif : forêt : [Image: 3.gif]
Etc.

Les routes (gifs transparents) :

0.gif : aucune route : [Image: route_0.gif]
1.gif : verticale : [Image: route_1.gif]
3.gif : virage bas/droite : [Image: route_3.gif]
etc.

Classe 'region' :

Enfin, j'utilise bcp les classes pour simplifier le travail, et éviter de recopier 50 fois le même code.
Dans notre fichier cron, nous utiliserons la classe 'region' qui reprends les valeur de la base region, ainsi que des fonctions pour sauvegarder/insérer/supprimer dont on ne se servira pas ici puisque dans ce tuto on ira chercher que les valeurs 'max_x', 'max_y' et 'nom' des regions, mais je donne la classe entière (bien que simplifiée).
Vous trouverez la classe 'region' tout à la fin du tuto.


Fichier "cron.php" :

Bon, les prérequis sont en place, la suite va être très simple maintenant.

Quand on programme une tâche cron, on doit indiquer le fichier qui sera exécuté, par exemple toutes les heures.

Créons donc le fichier "cron.php" ! :wow:
J'essai de commenter un maximum le code pour que tout soit clair.

On commence par les include/require dont on a besoin. Perso, j'inclu une fichier "fonctions.inc.php" qui contient mes fonctions de connexion et deconnexion à la base de données (connecter() et fermer()).

Code PHP :
<?
// ***** taches CRON *****

// --- includes générals
require_once("inc/fonctions.inc.php");

Ensuite, pour préparer le travail, on va créer 2 tableaux contenant les chemins vers nos images de relief et de route :

Code PHP :
<?php 
// --- Création des tableaux d'images relief/route ---
// tableau des 12 images de relief :
for ($i=0;$i<13;$i++) {
$rfic='img/relief/'.$i.'.gif';
$relief_img[$i] = imagecreatefromgif($rfic);
}
// tableau des 7 images de route :
for ($i=0;$i<8;$i++) {
$rfic='img/route/route_'.$i.'.gif';
$route_img[$i] = imagecreatefromgif($rfic);
}

On vide le répertoire contenant les cartes, pour virer les veilles images, maintenant inutiles.

Code PHP :
<?php 
// on supprime toutes les vieilles images, du repertoire des cartes
$dir = './cartes/';
$handdle = opendir($dir);
while(
$file = readdir($handdle)) {
if (
$file != "." && $file != "..")
{
unlink ("$dir/$file");
}
}

Il est temps de se connecter à la base de données.

On commence ensuite par une mise à jour du passage du cron.

Code PHP :
<?php 
// --- Connexion à la base de données
connecter();

// --- update le passage du cron dans la table 'xp_conf'
$time=time();
$req=mysql_query("UPDATE xp_conf SET valeur='$time' WHERE clef='cron'");

Le plus interessant compence maintenant !

On va d'abord chercher tous les ids de toutes les régions de notre univers, puisqu'on veut générer une carte pour chacune de ces régions. Tant qui'à faire on va les prendre dans l'ordre de leur id (order by id asc) mais c'est du luxe.

Code PHP :
<?php 
// --- charge les ids de toutes les regions
$region_req=mysql_query("SELECT id FROM xp_region ORDER BY id ASC");
$region_res=mysql_num_rows($region_req);

On ouvre une boucle FOR sur le resultat des régions trouvées, afin de traiter chaque région 1 par 1.

On commence direct par récupérer l'id de la région en cours.

Code PHP :
<?php 
// --- boucle des régions
for ($k=0;$k<$region_res;$k++) {
// - chope l'id de la région en cours
$id=mysql_result($region_req, $k, 'id');

On définit une variable 'region_fic' qui contient le chemin et le nom de l'image finale, de notre carte. Pour faciliter les chose, on met un nom générique qui contient l'id de la région.

Code PHP :
<?php 
// - Nom finale de l'image de la carte, d'apres l'id de la region
$region_fic="cartes/region_".$id.".png";

On ouvre maintenant une instance de notre classe 'region' et on charge notre région en cours.

Code PHP :
<?php 
$maregion
=new region($id);

On vérifie que la classe est bien chargé, et que son id est > à zéro. Car un 0, au niveau de la classe région, voudrait dire un chargement raté.

Code PHP :
<?php 
if ($maregion->id==$id && $id>0) {

Ok, la région est chargée.

On définit un niveau de zoom (une échelle). En fait c'est simple : c'est en pixels.

Un zoom de 100 affichera une carte de la région ou chaque domaine sera à l'échelle 1:1 puisque nos images de base, rapellez-vous, font 100px * 100px.

Un zoom de 50 fera une carte moitié moins grande : les images relief et route devront être réduites de 50%.

Il est bien sûr possible de mettre des zoom supérieurs à 100. Un zoom de 200 donnera une carte agrandi X2. Etc.
Mais n'oubliez pas que cela influe bcp sur le temps du script (fonctions GD) à calculer la redimmension des images, et surtout sur la taille et le poids de votre image finale !!

Pour notre exemple, on va prendre un zoom de 25, soit une carte réduite d'un quart. ca nous fera une petite carte, légère, dont le poids ira entre 1ko pour une carte vide et 20 ou 30ko pour des cartes de 20*20 à la base.

Code PHP :
<?php 
// définit l'échelle de la carte
$zoom=25;

On définit la largeur et la hauteur de notre carte, d'apres le zoom, et on ajoute 2 pixels pour faire au final une zolie bordure noire de 1pixels autour de la carte.

Code PHP :
<?php 
// largeur et hauteur de notre carte
$l=($maregion->max_x * $zoom)+2;
$h=($maregion->max_y * $zoom)+2;

On créé maintenant un objet image qui sera notre carte, avec la largeur /hauteur de la région. On ajouit 10 pixels au niveau hauteur, qui se répercuteront en bas, où on mettra le nom de la région et l'heure de la génération de cette carte.

Em même temps, on définit les couleurs de base de l'image, dont on aura besoin. La 1ere couleur définit (ici le blanc) donnera la couleur de fond de l'image nue.

Code PHP :
<?php 
        
// Création de l'objet image, notre carte, au largeur/hauteur de la region
        // +10px en bas pour mettre le nom de la région et l'heure de génération de la carte
        $img = imagecreate($l,$h+10);

// Définit les couleurs de base dont on aura besoin, dans la palette de l'image
$coul_0 = ImageColorAllocate($img, 255, 255, 255); // blanc
$coul_1 = ImageColorAllocate($img, 0, 0, 0); // noir

Maintenant on va choper dans la table des domaines le nombre de domaine connus pour la région en cours, en faisant un 'group by' sur la région.

Ansi, on pourra faire une boucle sur les régions qui contiennent au moins 1 domaine connu.
Pour les régions vides (inconnues), ca sera inutile et on générera directement une carte vide.

Code PHP :
<?php 
// cherche le nombre de domaines connus pour cette région
$nbdom_req=mysql_query("SELECT count(r) as nbdom FROM xp_dom WHERE r='$id' AND connu='1' GROUP BY r");
$nbdom_res=mysql_num_rows($nbdom_req);

// -- verif que la requete a abouti
if ($nbdom_res>0) {
// on chope le nombre de domaines connus pour cette région
$nbdom=mysql_result($nbdom_req, 0, 'nbdom');

// verif qu'il y a au moins 1 domaine connu dans la région
if ($nbdom>0) {

Ok il y a des domaines connus pour cette région.
On va donc quadriller la région en largeur/hauteur, afin de choper tous les domaines connus.

On commencé une boucle FOR sur la largeur de la région ($region->max_x) auquel on rajoute 1 pixels à cause de la zolie bordure de 1 pixels qu'il faut prendre en compte.

On en déduit X qui sera la positon X de ce domaine sur notre carte finale, d'après la position X du domaine (-1px cette fois car les coordonnées de l'objet image commenceent à 0 et finissent à à largeur-1 logiquement) multiplié par la valeur du zoom.

Code PHP :
<?php 
// - boucle sur la largeur de la région
for ($i=1;$i<($maregion->max_x+1);$i++) {
$x=($i-1)*$zoom;

Puis une seconde boucle FOR imbriquée sur la hauteur de la région. et on déduit Y de la même façon.

Code PHP :
<?php 
// - boucle sur la hauteur de la région
for ($j=1;$j<($maregion->max_y+1);$j++) {
$y=($j-1)*$zoom;

On cherche si le domaine à la position x/y de cette région est connu, en ne sélectionnant que 'relief' et 'route' dont on a besoin.

Code PHP :
<?php 
$req
=mysql_query("SELECT relief, route FROM xp_dom WHERE r='$id' AND x='$i' AND y='$j' AND connu='1'");
$res=mysql_num_rows($req);

Si le domaine est connu, on chope relief/route et on redimmensionne/copy les images du relief, puis par dessus ceux de la route, au bonne coordonnées 'x' et 'y', et à la taille 'zoom'

Code PHP :
<?php 
if ($res>0) { // le domaine est connu
// on chope son relief et sa route
$relief=mysql_result($req, 0, 'relief');
$route=mysql_result($req, 0, 'route');

// on redimensionne/copy le relief sur la carte, à la taille du zoom
imagecopyresampled($img, $relief_img[$relief], $x, $y, 0, 0, $zoom, $zoom, 100, 100);
// on redimensionne/copy le relief sur la carte, à la taille du zoom
imagecopyresampled($img, $route_img[$route], $x, $y, 0, 0, $zoom, $zoom, 100, 100);
}

On ferme nos 2 boucles FOR (larguer/hauteur) et les 2 IF (si contient un domaine connu).

Code PHP :
<?php 
}
}
}
}

Enfin, la carte de la région en cours est générée, on ajoute l epetit texte en bas avec le nom de la région et l adate de génération de la carte. On tyrace la zolie bordure. Et on sauve l'image de la carte !

Code PHP :
<?php 
// chope la date du moment et la formatte
$dhui=time();
$date=date("d/m/y H:i",$dhui);

// met en forme le texte "nom région" + "(date)"
$txt=$maregion->nom.' ('.$date.')';

// ajoute le texte tout en bas de la carte, dans les 10px en plus
ImageString ($img, 1, 3, $h+1,$txt , $coul_1);

// fais une bordure noire de 1px tout autour de la carte
imagerectangle($img, 0, 0, $l-1, $h-1, $coul_1);

// génère l'image et l'enregistre avec le nom voulu
ImagePng($img, $region_fic);

Fin de la boucle FOR sur chaque région, et sur le IF pour verif si la région était bien chargée. Détruit au passage l'objet 'region' qui a été utilisé.

Code PHP :
<?php 
}
unset (
$maregion);
}

Et pour finir :

Code PHP :
<?php 
// --- ferme la connexion à la base
fermer();

// --- détruit les images : relief, route
for ($i=0;$i<13;$i++) {
imagedestroy($relief_img[$i]);
}
for (
$i=0;$i<8;$i++) {
imagedestroy($route_img[$i]);
}
?>

C'est fini !!

Pour afficher une carte, on a donc besoin du numéro de la région à afficher, et de récupérer le 'time' du dernier passage du cron.

Par exemple, pour afficher la région numéro 3, on fera :

Code PHP :
<?php 
// chope le time du dernier passage cron
$temp_req=mysql_query("SELECT valeur FROM xp_conf WHERE clef='cron'");
$temp_res=mysql_num_rows($temp_req);
if (
$temp_res==1) { $timecron=mysql_result($temp_req, 0, 'valeur'); }
else {
$timecron=0; } // erreur ...

// on affiche l'image
echo '<img src="cartes/region_2_'.$timecron.'.png" alt="" />';


Voici tout le code de notre page "cron.php" :

Code PHP :
<?
// ***** taches CRON *****

// --- includes générals ---
require_once("inc/fonctions.inc.php");

// --- Création des tableaux d'images relief/route ---
// tableau des 12 images de relief :
for ($i=0;$i<13;$i++) {
$rfic='jeu/img/relief/'.$i.'.gif';
$relief_img[$i] = imagecreatefromgif($rfic);
}
// tableau des 7 images de relief :
for ($i=0;$i<8;$i++) {
$rfic='jeu/img/route/route_'.$i.'.gif';
$route_img[$i] = imagecreatefromgif($rfic);
}

// on supprime toutes les vieilles images, du repertoire des cartes
$dir = './cartes/';
$handdle = opendir($dir);
while(
$file = readdir($handdle)) {
if (
$file != "." && $file != "..")
{
unlink ("$dir/$file");
}
}

// --- Connexion à la base de données
connecter();

// --- update le passage du cron dans la table 'xp_conf'
$time=time();
$req=mysql_query("UPDATE xp_conf SET valeur='$time' WHERE clef='cron'");


// charge les ids de toutes les regions
$region_req=mysql_query("SELECT id FROM xp_region ORDER BY id ASC");
$region_res=mysql_num_rows($region_req);

// --- boucle des régions
for ($k=0;$k<$region_res;$k++) {
// - chope l'id de la région en coure
$id=mysql_result($region_req, $k, 'id');

// - Nom finale de l'image de la carte, d'apres l'id de la region + le time() actuel du passage du cron
$region_fic="cartes/region_".$id."_".$time.".png";

$maregion=new region($id);

if (
$maregion->id==$id && $id>0) {
// définit l'échelle de la carte
$zoom=25;

// largeur et hauteur de notre carte
$l=($maregion->max_x * $zoom)+2;
$h=($maregion->max_y * $zoom)+2;

// Création de l'objet image, notre carte, au largeur/hauteur de la region
// +10px en bas pour mettre le nom de la région et l'heure de génération de la carte
$img = imagecreate($l,$h+10);

// Définit les couleurs de base dont on aura besoin, dans la palette de l'image
$coul_0 = ImageColorAllocate($img, 255, 255, 255); // blanc
$coul_1 = ImageColorAllocate($img, 0, 0, 0); // noir

// cherche le nombre de domaines connus pour cette région
$nbdom_req=mysql_query("SELECT count(r) as nbdom FROM xp_dom WHERE r='$id' AND connu='1' GROUP BY r");
$nbdom_res=mysql_num_rows($nbdom_req);

// -- verif que la requete a abouti
if ($nbdom_res>0) {
// on chope le nombre de domaines connus pour cette région
$nbdom=mysql_result($nbdom_req, 0, 'nbdom');

// verif qu'il y a au moins 1 domaine connu dans la région
if ($nbdom>0) {

// - boucle sur la largeur de la région
for ($i=1;$i<($maregion->max_x+1);$i++) {
$x=($i-1)*$zoom;
// - boucle sur la hauteur de la région
for ($j=1;$j<($maregion->max_y+1);$j++) {
$y=($j-1)*$zoom;

$req=mysql_query("SELECT relief, route FROM xp_dom WHERE r='$id' AND x='$i' AND y='$j' AND connu='1'");
$res=mysql_num_rows($req);

if (
$res>0) { // le domaine est connu
// on chope son relief et sa route
$relief=mysql_result($req, 0, 'relief');
$route=mysql_result($req, 0, 'route');

// on redimensionne/copy le relief sur la carte, à la taille du zoom
imagecopyresampled($img, $relief_img[$relief], $x, $y, 0, 0, $zoom, $zoom, 100, 100);
// on redimensionne/copy le relief sur la carte, à la taille du zoom
imagecopyresampled($img, $route_img[$route], $x, $y, 0, 0, $zoom, $zoom, 100, 100);
}
}
}
}
}

// chope la date du moment et la formatte
$dhui=time();
$date=date("d/m/y H:i",$dhui);

// met en forme le texte "nom région" + "(date)"
$txt=$maregion->nom.' ('.$date.')';

// ajoute le texte tout en bas de la carte, dans les 10px en plus
ImageString ($img, 1, 3, $h+1,$txt , $coul_1);

// fais une bordure noire de 1px tout autour de la carte
imagerectangle($img, 0, 0, $l-1, $h-1, $coul_1);

// génère l'image et l'enregistre avec le nom voulu
ImagePng($img, $region_fic);
}
unset (
$maregion);
}

// --- ferme la connexion à la base
fermer();

// --- détruit les images : relief, route
for ($i=0;$i<13;$i++) {
imagedestroy($relief_img[$i]);
}
for (
$i=0;$i<8;$i++) {
imagedestroy($route_img[$i]);
}
?>


Et donc en prime la classe 'region' :

Code PHP :
<?php
// -------------------------------------------------------
// ------------ Classe 'region' ---------------------------
// -------------------------------------------------------

Class region {

// --- variables privées ---

var $id = 0; // id de la region
var $nom = ""; // nom de la région
var $id_pays = 0;
var
$relief = 0;
var
$max_x= 0; // largeur (255 max) en nb de domaine
var $max_y = 0; // hauteur (255 max) en nb de domaine

// --- fonction (constructeur) 'region' :  ---
// charge la rehion d'apres son id

function region($id_region=0) {
$r_id=intval($id_region);

if (
$r_id==0) {
// erreur
$this->do_reset();
}
else {
// select la region
$req=mysql_query("SELECT * FROM xp_region WHERE id='$r_id'");
$res=mysql_num_rows($req);

if (
$res!=1) {
// erreur
$this->do_reset();
}
// if ($res!=1)
else {
// charge la region
$res2=mysql_fetch_array($req);
$this->id = $res2["id"];
$this->nom = $res2["nom"];
$this->id_pays = $res2["id_pays"];
$this->relief = $res2["relief"];
$this->max_x = $res2["max_x"];
$this->max_y = $res2["max_y"];
}
// else/if ($res!=1)

} // fin : else/if ($r_id==0)

} // fin : function region


// --- fonction 'do_reset' ---
// remet toutes les vars à zéro
function do_reset() {
$this->id = 0;
$this->nom = "";
$this->id_pays = 0;
$this->relief = 0;
$this->max_x = 0;
$this->max_y = 0;
}
// fin : function do_reset


// --- fonction 'sauver' ---
// sauve la region dans la base
function sauver() {
if (
$this->id == 0) {
return
0;
}
// if
else {
$req=mysql_query("UPDATE xp_region SET nom='$this->nom', id_pays='$this->id_pays', relief='$this->relief', max_x='$this->max_x', max_y='$this->max_y' WHERE id='$this->id'");
$ret = mysql_affected_rows();
return
$ret;
}
// else
} // function


// --- fonction 'sauvernew' ---
// sauve la region dans la base comme nouvelle region
function sauvernew() {
$req=mysql_query("INSERT INTO xp_region VALUES('0', '$this->nom', '$this->id_pays', '$this->relief', '$this->max_x', '$this->max_y')");
$ret=mysql_insert_id();
return
$ret;
}
// function


// --- fonction 'supprimer' ---
// supprime une region
function supprimer() {
if (
$this->id!=0) {
// la supprime de la base
$req=mysql_query("DELETE FROM xp_region WHERE id='$this->id'");
$ret = mysql_affected_rows();
return
$ret;
}
else {
return
0;
}

}
// function

} // ------------ FIN : Classe 'region' ---------------------------
?>



RE: Image dynamique d'une carte avec les secteurs découverts d'une région - orditeck - 10-11-2006

L'aide pour ce script ce trouve à cette adresse :
http://www.jeuweb.org/board/showthread.php?tid=628