JeuWeb - Crée ton jeu par navigateur
[Reboot] Cités Myrmécéennes - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Les réalisations de la communauté (https://jeuweb.org/forumdisplay.php?fid=39)
+--- Forum : Jeux en développement (https://jeuweb.org/forumdisplay.php?fid=53)
+--- Sujet : [Reboot] Cités Myrmécéennes (/showthread.php?tid=7998)

Pages : 1 2 3 4


RE: [Reboot] Cités Myrmécéennes - L'Omniscient - 06-04-2020

Le graphisme est très sympa. A ta place je séparerais peut-être plus les mots par des "_", ça permettra au joueur d'avoir une meilleur vue d'ensemble, mais sinon visuellement ça a l'air top. Je rajouterai peut-être un petit + ou - pour fermer les catégories. Sinon rien à redire ! Big Grin

Hâte d'avoir un premier aperçu Big Grin (Ma fourmi est toujours offerte au passage, avec ou sans yeux Big Grin )


RE: [Reboot] Cités Myrmécéennes - Xenos - 06-04-2020

Cool de voir le jeu avancer : ) A quand les premiers tests?!

Je suis un peu surpris de devoir passer par un CONCAT pour la diff de temps; je suis plus habitué à la syntaxe MySQL type DATEADD(NOW(), INTERVAL -:hunt_speed MINUTES)
Perso, je définirait updated_at à NOW lors de la création de la ligne dans hunts. Ca évite de rendre la colonne nullable, et ca garde une sémantique encore plus simple à la colonne. Ca te fait aussi sauter la condition sur le created at (puisque updated_at n'est plus NULLable, et que donc, le 2nd test du AND implique [par logique du jeu] que le 1er test du AND est vrai et peut donc sauter) et ça rend la query réellement lisible Smile

En revanche, le delta entre deux appels ne devra pas être "trop grand" car là, tu ne peux faire qu'un saut de 1 step.

Nota: pas besoin de Laravel ou d'un quelconque framework pour faire du cron dans la DB: y'a les EVENT pour ça, qui permettent de faire des queries SQL (perso, de lancer une procédure stockée qui fait la simulation du jeu) toutes les X jours/heures/minutes/secondes (y'a même les "cron à la seconde" via ça, oui...!) L'un des atouts IMO (mais faut faire un jeu full-sql), c'est qu'on n'a pas besoin de faire communiquer un client (php, cron, qu'importe) avec le serveur SQL: on n'est donc pas limité par le nb de connexions max, on n'a pas de risque de "unreachable DB" si jamais l'hébergement web est en rade, et on peut lancer "l'event" à la mano (ie: si l'event c'est juste CALL procedure... alors on peut faire directement un CALL procedure pour simuler le jeu).

Mais en revanche, comme toute approche full SQL, niveau débuggeur, c'est "démerde-toi" (j'ai la triste impression de faire du JS du coup x) )... Là, je suis personnellement preneur de toute solution (mais je n'en ai jamais trouvée!) pour faire du pas à pas dans la procédure, ou à défaut, avoir le n° de ligne de la procédure quand une erreur survient (c'est d'un chiant de ne pas même avoir cette info! :o )

Bonne continuation! Smile

@L'Omniscient: c'est pas une vue pour le joueur hein, c'est la vue de la liste des tests du code...


RE: [Reboot] Cités Myrmécéennes - Maz - 06-04-2020

Merci les copains pour les encouragements Smile.

J'ai effectivement eu un soucis sans le CONCAT, j'ai essayé plusieurs syntaxe sans succès (dont celle évoquée). C'est effectivement très verbeux mais je n'ai pas trouvé de syntaxe qui passes sous Postgre pour le moment. Tiens histoire que ça te piques les yeux:
NOW() + CONCAT(:dev_duration::INT,' MINUTES ', :index_{$index}::INT * :offset::INT, ' SECONDS')::INTERVAL
Ici la contrainte été d'arriver à sécuriser les trois variables. Je n'ai trouvé que le CONCAT pour pouvoir les passer. Après je peux calculer en PHP le index * offset c'est vrai et passer juste un offset_{$index}. C'est bien pour ça que ma prochaine étape est la relecture/refacto :]

Il est vrai que le je peut éviter le updated_at nullable et éviter le AND dans ma requête, je vais modifier en ce sens, mais je pense quand même garder le created_at pour l'historique dans le gameplay: afficher le début/fin de la chasse, etc...

Et oui l'Omniscient, j'espère bien que tu ne crois pas que ceci est mon jeu... Ce ne sont que mes tests.


RE: [Reboot] Cités Myrmécéennes - Xenos - 06-04-2020

Vi, tu peux garder le created pour des questions d'historique et non de calcul. Perso, ce que j'ai comme "convention" dans ce genre de cas, c'est d'avoir une colonne "last_computation_date" ou assimilé, qui sert à faire lesdits calculs (et qui peut parfois différer d'un éventuel "last update", même si c'est rare).

Le risque avec un concat, c'est que le résultat est ensuite ré-interprété comme, ici, un INTERVAL. Ca va que tu n'as que des integers (donc je ne vois pas trop ce qu'on pourrait planter), mais imaginons que ce soient des STRING à la place, alors si la valeur "dev_duration" est "1 HOURS - 0", le concat devient "1 HOURS - 0 MINUTES + ... SECONDS" et tu manipules l'intervalle...
Ou encore (je ne sais pas si cette syntaxe serait permise), si la valeur de dev_duration est un nom de fonction, que se passe-t-il? Un dev_duration = "SLEEP(10) + 2" qui donne un intervalle "SLEEP(10) + 2 MINUTES + ... SECONDS" va-t-il faire attendre le serveur SQL pendant 10 seconds? Ce serait à vérifier via la doc de pgsql

Sinon, tu peux basculer sur du timestamp. En MySQL, tu pourrais ainsi faire:
Code :
UNIX_TIMESTAMP + (:dev_duration * 60 + :index$index * :offset)

(bon, après, ce que je trouve d'autant plus chaud, c'est le $index en plein milieu de la query qui rend le prepare un peu "inutile"; vaudrait limite mieux passer un json au SQL, et lui faire extraire le n-eme élément)


RE: [Reboot] Cités Myrmécéennes - Maz - 06-04-2020

Le $index au milieu est parce-que je suis forcé de "fabriquer" la requête, j'ai ce soucis à plusieurs endroit dans le code actuellement ou je veut insérer/update/select plusieurs lignes et pour m'assurer de la sécurité avec pdo je dois créer dynamiquement la requête, genre ici sur un delete:

public static function deleteMany($ants) {
    // If objects are passed, search for uuid only
    if(is_object($ants[0])) {
        // If no uuid are presents in the array, throw an error
        if(!isset($ants[0]->uuid)) {
            throw new ErrorException('No uuid present in the array of object.');
        }

        $ants = array_map(static function($ant) {
            return $ant->uuid;
        }, $ants);
    }

    // Create the in statement
    $in = str_repeat('?,', count($ants) - 1) . '?';

    return DB::deleteReturn("DELETE FROM ants WHERE uuid IN ({$in}) RETURNING uuid", $ants);
}

J'ai pas trouvé meilleur solution pour passer ce type de limitation de PDO.


RE: [Reboot] Cités Myrmécéennes - Xenos - 06-04-2020

C'est vrai que dans ce genre d'exemple, le "$in" étant créé au même endroit que la query, ça me semble lisible & fiable.
Après, je n'ai perso pas tellement ce problème, puisqu'étant dans une procédure stockée, je peux boucler du DELETE FROM ants WHERE id = :id pour chaque id. TU peux éventuellement passer par du JSON_TABLE si cela existe en pgsql (DELETE FROM ants WHERE uuid IN (SELECT id FROM JSON_TABLE(:in COLUMN id INT UNSIGNED) AS t) un truc du genre).

Bon, cela dit, ça me choque en fait moins dans le cas du "in" car la query est variable parce que le nb d'élément l'est. Mais dans l'autre exemple, c'est carrément le nom du placeholder qui est variable, et là, j'ai perso bien plus de mal : )

PS: tu peux utiliser $ants = array_column($ants, 'uuid') plutôt qu'un array map. Et perso, je ne toucherai pas aux variables d'entrée, je les considèrerai un peu comme "final" si c'était du java (et je trouve en fait encore plus gênant de pouvoir passer pifométriquemment des objets ou des ids à cette méthode... j'aurai pris l'un ou l'autre, point, quitte à rajouter un deleteManyUuid(...) ou un deleteManyObject(...) pour faire la distinction). Tant qu'à faire, du coup, si tu gardes des objets, mieux vaut vérifier leur typage, plutôt que l'existence d'un uuid if ($ants[0] instanceof Ants) plutôt, pour ne pas passer un autre objet (qui aurait aussi le champ uuid mais qui ne serait pas une ant)?


RE: [Reboot] Cités Myrmécéennes - Maz - 06-04-2020

L'autre exemple quasi-complet(juste le bout pour la création de la requête), qui est régis par les mêmes règles que le in c'est à dire nombre de paramètre variable:

public static function createMany(City $city, Array $ants)
{
    // Data shared by all the inserts
    $data['city_uuid'] = $city->uuid;
    $data['position'] = DB:ConfusedanitizePoint($city->position);
    $data['dev_duration'] = $bindings = $city->buildings->royal_room->nurseryDevelopmentTimes()['end'];
    $data['offset'] = $city->buildings->royal_room->getLevelFor('born_time_offset');

    $sql = '';
    foreach ($ants as $index => $ant) {
        $data["caste_{$index}"] = $ant['caste'];
        $data["index_{$index}"] = $index;
        $will_die_statement = isset($ant['will_die']) && $ant['will_die'] ? "NOW() + CONCAT(FLOOR(RANDOM() * {$data['dev_duration']})::INT, ' MINUTES')::INTERVAL" : 'NULL';

        $sql .= "(:city_uuid, ST_GeomFromText(:position, 4326), NOW() + CONCAT(:dev_duration::INT,' MINUTES ', :index_{$index}::INT * :offset::INT, ' SECONDS')::INTERVAL, :caste_{$index}, {$will_die_statement}),";
    }

    // Create the full SQL Statement
    $sql = 'INSERT INTO ants (city_uuid, position, born_time, caste, dead_time) VALUES ' . substr($sql, 0, -1) . ' RETURNING *, ST_X(position) as x, ST_Y(position) as y';

    $insertion = DB::insertReturn($sql, $data);

La solution du JSON est envisageable, je vais m'y intéresser car ce type de requête va être parsemé dans mon code donc je prends toute bonne idée Smile


RE: [Reboot] Cités Myrmécéennes - Meraxes - 06-04-2020

Maz a écrit :(updated_at IS NULL OR updated_at < NOW() - CONCAT(:hunt_speed::INT, ' minutes')::INTERVAL)

Maz a écrit :NOW() + CONCAT(:dev_duration::INT,' MINUTES ', :index_{$index}::INT * :offset::INT, ' SECONDS')::INTERVAL

Tu peux utiliser la fonction make_interval() :

Code :
SELECT NOW() - make_interval(mins := :hunt_speed) FROM ...

Code :
SELECT NOW() + make_interval(0,0,0,0,0,:dev_duration,  :index_{$index} * :offset) FROM ...



RE: [Reboot] Cités Myrmécéennes - Maz - 08-04-2020

(06-04-2020, 07:39 PM)Meraxes a écrit : Tu peux utiliser la fonction make_interval() :

Code :
SELECT NOW() - make_interval(mins := :hunt_speed) FROM ...

Code :
SELECT NOW() + make_interval(0,0,0,0,0,:dev_duration,  :index_{$index} * :offset) FROM ...
Génial! J'ai repris toute mes fonctions ou j'utilisais mon concat avec ta solution :] Je suis content d'avoir un truc plus joli pour cette partie là qui me chagrinais depuis le début.

J'ai codé mes premières fonctions SQL pour le jeu aujourd'hui:
Une première pour généré un nombre aléatoire. Facile mais nécessaire pour la seconde qui génères un point aléatoirement dans une zone autour d'un autre point donnée en saisissant une distance max:

CREATE OR REPLACE FUNCTION public.random_between(low integer, high integer)
RETURNS integer
    LANGUAGE plpgsql
    STRICT
    AS $function$
BEGIN
    RETURN floor(random()* (high-low + 1) + low);
END;
$function$
;

CREATE OR REPLACE FUNCTION public.random_point_around(target_point geometry, expand_size integer)
    RETURNS geometry
    LANGUAGE plpgsql
AS $function$
    DECLARE
    point_x  INTEGER := ST_X(target_point);
    point_y  INTEGER := ST_Y(target_point);
    random_x INTEGER := random_between(point_x - expand_size, point_x + expand_size);
    random_y INTEGER := random_between(point_y - expand_size, point_y + expand_size);
    begin
    RETURN ST_GeomFromText(CONCAT('POINT(', random_x, ' ',  random_y, ')'), 4326);
    END; $function$
;



RE: [Reboot] Cités Myrmécéennes - L'Omniscient - 10-04-2020

Le Maz carbure à fond et ne peut plus s'arrêter. Bientôt sur la ligne d'arrivée ! Allez Maz allez Maz !