Compare commits

..

43 Commits
cleo ... main

Author SHA1 Message Date
Cleo EIRAS a3cecbc9d6 👻
15 hours ago
samuel a02b25f61b Admin panel fixed
23 hours ago
samuel 6d563bd831 Admin : routes access control for /users + possibility to extend elsewhere
23 hours ago
Mathis MOULIN 7f602aec33 Ajout de la liason home avec combat !
1 day ago
Mathis MOULIN 2a5d0ccb5a Faux push
1 day ago
Mathis MOULIN 51b56c44b3 Tout fonction reproduction fonction en back et front
1 day ago
Mathis MOULIN b7e08bb775 Oublie fichier
1 day ago
Mathis MOULIN d0bdb9c067 Ajout Front Jade v1 WIP
1 day ago
Mathis MOULIN a66a4a3d95 Merge Samuel
1 day ago
Mathis MOULIN 0c287484d2 Hierachie familliale emoji
1 day ago
samuel 0c51e7fbd7 Add user_emojis -> emojis of a user fix + refactor + Add Service
2 days ago
Jade VAN BRABANDT 33237d6ca9 idk i'll see later
2 days ago
Jade VAN BRABANDT 0890abc78c Reproduction, La DA es ter
2 days ago
Jade VAN BRABANDT 3558e4c289 IHateSymfony
2 days ago
Mathis MOULIN 88da18817d rebase on last version mathis
2 days ago
Jade VAN BRABANDT f0bc442188 Merge remote-tracking branch 'origin/mathis' into Jade-Front
2 days ago
Mathis MOULIN feef24045c Modif fight for anim
2 days ago
Mathis MOULIN 535531440f Merge taff Raff & Samuel
2 days ago
Jade VAN BRABANDT c300cfee26 Reproduction with id
2 days ago
Jade VAN BRABANDT 8d4b76c309 Merge remote-tracking branch 'origin/cleo' into Jade-Front
3 days ago
Jade VAN BRABANDT 0af007b902 Change url
3 days ago
samuel 43e653bb06 API Plateforme entity ressources
3 days ago
samuel e57722cabb Auth with Symfony Form
3 days ago
Raphael LACOTE 245f301602 Merge remote-tracking branch 'origin/mathis' into raph_combat
6 days ago
Raphael LACOTE e3fe5fb5f4 Merge remote-tracking branch 'origin/cleo' into raph_combat
6 days ago
Mathis MOULIN ebeeac349b Ajout table stock d'emoji disponible dans l'application + commande pour remplir la table
1 week ago
Jade VAN BRABANDT ced39abea8 Reproduction, La DA es ter
1 week ago
Raphael LACOTE 318734b2f2 Combats
1 week ago
Raphael LACOTE 4713eab595 Modif de home.js pour ajouter le combat
1 week ago
Mathis MOULIN c4fbcb8709 Merge remote-tracking branch 'origin/cleo' into mathis
1 week ago
Mathis MOULIN 732861cd6f Merge remote-tracking branch 'origin' into mathis
1 week ago
Mathis MOULIN c01f3525ff Ajout 2 ligne émoji + pagination
1 week ago
Raphael LACOTE 9c931cc616 Merge de la branche de Cleo
1 week ago
Raphael LACOTE f5fde68b14 Merge remote-tracking branch 'origin/cleo' into raph_combat
1 week ago
Mathis MOULIN c040ee4a6f Amélioration colléction emoji + débug séléction pour combat repro
1 week ago
Jade VAN BRABANDT 0bbd984d79 IHateSymfony
1 week ago
Raphael LACOTE 13186d6dc0 Modif du combat (c est une route maintenant)
1 week ago
Jade VAN BRABANDT 3b314f30e5 Merge remote-tracking branch 'origin/cleo' into Jade-Front
1 week ago
Raphael LACOTE 891f37de5f Modif du combat
1 week ago
Jade VAN BRABANDT ca6702ea60 Merge cleo
1 week ago
Raphael LACOTE 41916fc9b0 Ajout du combat
1 week ago
BelsethUwU 44309c2b9b Implements the use of custom emoji
2 weeks ago
BelsethUwU 36893c8bc8 Combat page with animation
2 weeks ago

@ -20,3 +20,6 @@ APP_SECRET=e10d1768cebd3187e908e91448a3086a
###< symfony/framework-bundle ### ###< symfony/framework-bundle ###
MESSENGER_TRANSPORT_DSN=sync:// MESSENGER_TRANSPORT_DSN=sync://
DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db" DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"
###> nelmio/cors-bundle ###
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
###< nelmio/cors-bundle ###

@ -0,0 +1,13 @@
# How to run
`composer update`
`php bin/console make:migration`
`php bin/console doctrine:migrations:migrate`
`php bin/console app:populateDB`
`php bin/console app:popDBEmojiAvai`
`symfony server:start`

@ -9,10 +9,12 @@
"php": ">=8.1", "php": ">=8.1",
"ext-ctype": "*", "ext-ctype": "*",
"ext-iconv": "*", "ext-iconv": "*",
"api-platform/core": "^3.2",
"doctrine/dbal": "^3", "doctrine/dbal": "^3",
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13",
"doctrine/doctrine-migrations-bundle": "^3.4", "doctrine/doctrine-migrations-bundle": "^3.4",
"doctrine/orm": "^3.3", "doctrine/orm": "^3.3",
"nelmio/cors-bundle": "^2.5",
"phpdocumentor/reflection-docblock": "^5.6", "phpdocumentor/reflection-docblock": "^5.6",
"phpstan/phpdoc-parser": "^1.14", "phpstan/phpdoc-parser": "^1.14",
"symfony/asset": "6.1.*", "symfony/asset": "6.1.*",

291
composer.lock generated

@ -4,8 +4,175 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "a904471bc7e1136f2c1810044504fcc9", "content-hash": "e4da0873e331d870ab5d4a8fce5d1fc4",
"packages": [ "packages": [
{
"name": "api-platform/core",
"version": "v3.2.26",
"source": {
"type": "git",
"url": "https://github.com/api-platform/core.git",
"reference": "9edacabcfff3a62c29325e1e91af3269850954cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/api-platform/core/zipball/9edacabcfff3a62c29325e1e91af3269850954cb",
"reference": "9edacabcfff3a62c29325e1e91af3269850954cb",
"shasum": ""
},
"require": {
"doctrine/inflector": "^1.0 || ^2.0",
"php": ">=8.1",
"psr/cache": "^1.0 || ^2.0 || ^3.0",
"psr/container": "^1.0 || ^2.0",
"symfony/deprecation-contracts": "^3.1",
"symfony/http-foundation": "^6.1 || ^7.0",
"symfony/http-kernel": "^6.1 || ^7.0",
"symfony/property-access": "^6.1 || ^7.0",
"symfony/property-info": "^6.1 || ^7.0",
"symfony/serializer": "^6.1 || ^7.0",
"symfony/translation-contracts": "^3.3",
"symfony/web-link": "^6.1 || ^7.0",
"willdurand/negotiation": "^3.0"
},
"conflict": {
"doctrine/common": "<3.2.2",
"doctrine/dbal": "<2.10",
"doctrine/mongodb-odm": "<2.4",
"doctrine/orm": "<2.14.0",
"doctrine/persistence": "<1.3",
"elasticsearch/elasticsearch": ">=8.0,<8.4",
"phpspec/prophecy": "<1.15",
"phpunit/phpunit": "<9.5",
"symfony/framework-bundle": "6.4.6 || 7.0.6",
"symfony/var-exporter": "<6.1.1"
},
"require-dev": {
"behat/behat": "^3.11",
"behat/mink": "^1.9",
"doctrine/cache": "^1.11 || ^2.1",
"doctrine/common": "^3.2.2",
"doctrine/dbal": "^3.4.0",
"doctrine/doctrine-bundle": "^1.12 || ^2.0",
"doctrine/mongodb-odm": "^2.2",
"doctrine/mongodb-odm-bundle": "^4.0 || ^5.0",
"doctrine/orm": "^2.14 || ^3.0",
"elasticsearch/elasticsearch": "^7.11 || ^8.4",
"friends-of-behat/mink-browserkit-driver": "^1.3.1",
"friends-of-behat/mink-extension": "^2.2",
"friends-of-behat/symfony-extension": "^2.1",
"guzzlehttp/guzzle": "^6.0 || ^7.0",
"jangregor/phpstan-prophecy": "^1.0",
"justinrainbow/json-schema": "^5.2.1",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/extension-installer": "^1.1",
"phpstan/phpdoc-parser": "^1.13",
"phpstan/phpstan": "^1.1",
"phpstan/phpstan-doctrine": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-symfony": "^1.0",
"phpunit/phpunit": "^9.5",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"ramsey/uuid": "^3.9.7 || ^4.0",
"ramsey/uuid-doctrine": "^1.4 || ^2.0",
"sebastian/comparator": "<5.0",
"soyuka/contexts": "v3.3.9",
"soyuka/stubs-mongodb": "^1.0",
"symfony/asset": "^6.1 || ^7.0",
"symfony/browser-kit": "^6.1 || ^7.0",
"symfony/cache": "^6.1 || ^7.0",
"symfony/config": "^6.1 || ^7.0",
"symfony/console": "^6.1 || ^7.0",
"symfony/css-selector": "^6.1 || ^7.0",
"symfony/dependency-injection": "^6.1 || ^7.0.12",
"symfony/doctrine-bridge": "^6.1 || ^7.0",
"symfony/dom-crawler": "^6.1 || ^7.0",
"symfony/error-handler": "^6.1 || ^7.0",
"symfony/event-dispatcher": "^6.1 || ^7.0",
"symfony/expression-language": "^6.1 || ^7.0",
"symfony/finder": "^6.1 || ^7.0",
"symfony/form": "^6.1 || ^7.0",
"symfony/framework-bundle": "^6.1 || ^7.0",
"symfony/http-client": "^6.1 || ^7.0",
"symfony/intl": "^6.1 || ^7.0",
"symfony/maker-bundle": "^1.24",
"symfony/mercure-bundle": "*",
"symfony/messenger": "^6.1 || ^7.0",
"symfony/phpunit-bridge": "^6.1 || ^7.0",
"symfony/routing": "^6.1 || ^7.0",
"symfony/security-bundle": "^6.1 || ^7.0",
"symfony/security-core": "^6.1 || ^7.0",
"symfony/stopwatch": "^6.1 || ^7.0",
"symfony/twig-bundle": "^6.1 || ^7.0",
"symfony/uid": "^6.1 || ^7.0",
"symfony/validator": "^6.1 || ^7.0",
"symfony/web-profiler-bundle": "^6.1 || ^7.0",
"symfony/yaml": "^6.1 || ^7.0",
"twig/twig": "^1.42.3 || ^2.12 || ^3.0",
"webonyx/graphql-php": "^14.0 || ^15.0"
},
"suggest": {
"doctrine/mongodb-odm-bundle": "To support MongoDB. Only versions 4.0 and later are supported.",
"elasticsearch/elasticsearch": "To support Elasticsearch.",
"ocramius/package-versions": "To display the API Platform's version in the debug bar.",
"phpstan/phpdoc-parser": "To support extracting metadata from PHPDoc.",
"psr/cache-implementation": "To use metadata caching.",
"ramsey/uuid": "To support Ramsey's UUID identifiers.",
"symfony/cache": "To have metadata caching when using Symfony integration.",
"symfony/config": "To load XML configuration files.",
"symfony/expression-language": "To use authorization features.",
"symfony/http-client": "To use the HTTP cache invalidation system.",
"symfony/messenger": "To support messenger integration.",
"symfony/security": "To use authorization features.",
"symfony/twig-bundle": "To use the Swagger UI integration.",
"symfony/uid": "To support Symfony UUID/ULID identifiers.",
"symfony/web-profiler-bundle": "To use the data collector.",
"webonyx/graphql-php": "To support GraphQL."
},
"type": "library",
"extra": {
"symfony": {
"require": "^6.1 || ^7.0"
},
"branch-alias": {
"dev-main": "3.3.x-dev"
}
},
"autoload": {
"psr-4": {
"ApiPlatform\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kévin Dunglas",
"email": "kevin@dunglas.fr",
"homepage": "https://dunglas.fr"
}
],
"description": "Build a fully-featured hypermedia or GraphQL API in minutes!",
"homepage": "https://api-platform.com",
"keywords": [
"Hydra",
"JSON-LD",
"api",
"graphql",
"hal",
"jsonapi",
"openapi",
"rest",
"swagger"
],
"support": {
"issues": "https://github.com/api-platform/core/issues",
"source": "https://github.com/api-platform/core/tree/v3.2.26"
},
"time": "2024-07-19T15:06:38+00:00"
},
{ {
"name": "doctrine/cache", "name": "doctrine/cache",
"version": "2.2.0", "version": "2.2.0",
@ -1392,6 +1559,68 @@
], ],
"time": "2025-03-24T10:02:05+00:00" "time": "2025-03-24T10:02:05+00:00"
}, },
{
"name": "nelmio/cors-bundle",
"version": "2.5.0",
"source": {
"type": "git",
"url": "https://github.com/nelmio/NelmioCorsBundle.git",
"reference": "3a526fe025cd20e04a6a11370cf5ab28dbb5a544"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/3a526fe025cd20e04a6a11370cf5ab28dbb5a544",
"reference": "3a526fe025cd20e04a6a11370cf5ab28dbb5a544",
"shasum": ""
},
"require": {
"psr/log": "^1.0 || ^2.0 || ^3.0",
"symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0"
},
"require-dev": {
"mockery/mockery": "^1.3.6",
"symfony/phpunit-bridge": "^5.4 || ^6.0 || ^7.0"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"Nelmio\\CorsBundle\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nelmio",
"homepage": "http://nelm.io"
},
{
"name": "Symfony Community",
"homepage": "https://github.com/nelmio/NelmioCorsBundle/contributors"
}
],
"description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony application",
"keywords": [
"api",
"cors",
"crossdomain"
],
"support": {
"issues": "https://github.com/nelmio/NelmioCorsBundle/issues",
"source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.5.0"
},
"time": "2024-06-24T21:25:28+00:00"
},
{ {
"name": "phpdocumentor/reflection-common", "name": "phpdocumentor/reflection-common",
"version": "2.2.0", "version": "2.2.0",
@ -6913,6 +7142,62 @@
"source": "https://github.com/webmozarts/assert/tree/1.11.0" "source": "https://github.com/webmozarts/assert/tree/1.11.0"
}, },
"time": "2022-06-03T18:03:27+00:00" "time": "2022-06-03T18:03:27+00:00"
},
{
"name": "willdurand/negotiation",
"version": "3.1.0",
"source": {
"type": "git",
"url": "https://github.com/willdurand/Negotiation.git",
"reference": "68e9ea0553ef6e2ee8db5c1d98829f111e623ec2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/willdurand/Negotiation/zipball/68e9ea0553ef6e2ee8db5c1d98829f111e623ec2",
"reference": "68e9ea0553ef6e2ee8db5c1d98829f111e623ec2",
"shasum": ""
},
"require": {
"php": ">=7.1.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"Negotiation\\": "src/Negotiation"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "William Durand",
"email": "will+git@drnd.me"
}
],
"description": "Content Negotiation tools for PHP provided as a standalone library.",
"homepage": "http://williamdurand.fr/Negotiation/",
"keywords": [
"accept",
"content",
"format",
"header",
"negotiation"
],
"support": {
"issues": "https://github.com/willdurand/Negotiation/issues",
"source": "https://github.com/willdurand/Negotiation/tree/3.1.0"
},
"time": "2022-01-30T20:08:53+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
@ -9201,7 +9486,7 @@
], ],
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": [], "stability-flags": {},
"prefer-stable": true, "prefer-stable": true,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
@ -9209,6 +9494,6 @@
"ext-ctype": "*", "ext-ctype": "*",
"ext-iconv": "*" "ext-iconv": "*"
}, },
"platform-dev": [], "platform-dev": {},
"plugin-api-version": "2.6.0" "plugin-api-version": "2.6.0"
} }

@ -11,4 +11,6 @@ return [
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
]; ];

@ -0,0 +1,18 @@
api_platform:
title: Hello API Platform
version: 1.0.0
formats:
jsonld: ['application/ld+json']
docs_formats:
jsonld: ['application/ld+json']
jsonopenapi: ['application/vnd.openapi+json']
html: ['text/html']
defaults:
stateless: true
cache_headers:
vary: ['Content-Type', 'Authorization', 'Origin']
extra_properties:
standard_put: true
rfc_7807_compliant_errors: true
event_listeners_backward_compatibility_layer: false
keep_legacy_inflector: false

@ -1,6 +1,6 @@
# see https://symfony.com/doc/current/reference/configuration/framework.html # see https://symfony.com/doc/current/reference/configuration/framework.html
framework: framework:
secret: '%env(APP_SECRET)%' secret: "%env(APP_SECRET)%"
#csrf_protection: true #csrf_protection: true
http_method_override: false http_method_override: false

@ -0,0 +1,10 @@
nelmio_cors:
defaults:
origin_regex: true
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
allow_headers: ['Content-Type', 'Authorization']
expose_headers: ['Link']
max_age: 3600
paths:
'^/': null

@ -4,14 +4,30 @@ security:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers: providers:
users_in_memory: { memory: null } # used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: username
firewalls: firewalls:
dev: dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/ pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false security: false
main: main:
lazy: true lazy: true
provider: users_in_memory provider: app_user_provider
custom_authenticator: App\Security\LoginFormAuthenticator
logout:
path: app_logout
target: app_login
entry_point: App\Security\LoginFormAuthenticator
remember_me:
secret: '%kernel.secret%'
lifetime: 604800
path: /
always_remember_me: true
# activate different ways to authenticate # activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall # https://symfony.com/doc/current/security.html#the-firewall
@ -22,11 +38,16 @@ security:
# Easy way to control access for large sections of your site # Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used # Note: Only the *first* access control that matches will be used
access_control: access_control:
# - { path: ^/admin, roles: ROLE_ADMIN } - { path: ^/login, roles: PUBLIC_ACCESS }
# - { path: ^/profile, roles: ROLE_USER } - { path: ^/register, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: ROLE_USER }
when@test: when@test:
security: security:
firewalls:
main:
security: false
password_hashers: password_hashers:
# By default, password hashers are resource intensive and take time. This is # By default, password hashers are resource intensive and take time. This is
# important to generate secure password hashes. In tests however, secure hashes # important to generate secure password hashes. In tests however, secure hashes

@ -0,0 +1,4 @@
api_platform:
resource: .
type: api_platform
prefix: /api

@ -1,44 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250529203532 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
CREATE TABLE emoji (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, nom VARCHAR(255) NOT NULL, code VARCHAR(255) NOT NULL, force DOUBLE PRECISION NOT NULL, robustesse DOUBLE PRECISION NOT NULL, intelligence DOUBLE PRECISION NOT NULL, vitesse DOUBLE PRECISION NOT NULL, nb_combat_gagne INTEGER NOT NULL, rarete INTEGER NOT NULL)
SQL);
$this->addSql(<<<'SQL'
CREATE TABLE rarity (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(50) NOT NULL, drop_rate DOUBLE PRECISION NOT NULL)
SQL);
$this->addSql(<<<'SQL'
CREATE UNIQUE INDEX UNIQ_B7C0BE465E237E06 ON rarity (name)
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
DROP TABLE emoji
SQL);
$this->addSql(<<<'SQL'
DROP TABLE rarity
SQL);
}
}

@ -1,62 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250529204111 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
CREATE TEMPORARY TABLE __temp__emoji AS SELECT id, nom, code, intelligence FROM emoji
SQL);
$this->addSql(<<<'SQL'
DROP TABLE emoji
SQL);
$this->addSql(<<<'SQL'
CREATE TABLE emoji (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, rarity_id INTEGER NOT NULL, name VARCHAR(255) NOT NULL, code VARCHAR(255) NOT NULL, intelligence DOUBLE PRECISION NOT NULL, strength DOUBLE PRECISION NOT NULL, toughness DOUBLE PRECISION NOT NULL, speed DOUBLE PRECISION NOT NULL, fights_won INTEGER NOT NULL, CONSTRAINT FK_B64BF632F3747573 FOREIGN KEY (rarity_id) REFERENCES rarity (id) NOT DEFERRABLE INITIALLY IMMEDIATE)
SQL);
$this->addSql(<<<'SQL'
INSERT INTO emoji (id, name, code, intelligence) SELECT id, nom, code, intelligence FROM __temp__emoji
SQL);
$this->addSql(<<<'SQL'
DROP TABLE __temp__emoji
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_B64BF632F3747573 ON emoji (rarity_id)
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
CREATE TEMPORARY TABLE __temp__emoji AS SELECT id, name, code, intelligence FROM emoji
SQL);
$this->addSql(<<<'SQL'
DROP TABLE emoji
SQL);
$this->addSql(<<<'SQL'
CREATE TABLE emoji (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, nom VARCHAR(255) NOT NULL, code VARCHAR(255) NOT NULL, intelligence DOUBLE PRECISION NOT NULL, force DOUBLE PRECISION NOT NULL, robustesse DOUBLE PRECISION NOT NULL, vitesse DOUBLE PRECISION NOT NULL, nb_combat_gagne INTEGER NOT NULL, rarete INTEGER NOT NULL)
SQL);
$this->addSql(<<<'SQL'
INSERT INTO emoji (id, nom, code, intelligence) SELECT id, name, code, intelligence FROM __temp__emoji
SQL);
$this->addSql(<<<'SQL'
DROP TABLE __temp__emoji
SQL);
}
}

@ -1,71 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250530163653 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
CREATE TEMPORARY TABLE __temp__emoji AS SELECT id, rarity_id, name, code, intelligence, strength, toughness, speed, fights_won FROM emoji
SQL);
$this->addSql(<<<'SQL'
DROP TABLE emoji
SQL);
$this->addSql(<<<'SQL'
CREATE TABLE emoji (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, rarity_id INTEGER NOT NULL, parent1_id INTEGER DEFAULT NULL, parent2_id INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, code VARCHAR(255) NOT NULL, intelligence DOUBLE PRECISION NOT NULL, strength DOUBLE PRECISION NOT NULL, toughness DOUBLE PRECISION NOT NULL, speed DOUBLE PRECISION NOT NULL, fights_won INTEGER NOT NULL, CONSTRAINT FK_B64BF632F3747573 FOREIGN KEY (rarity_id) REFERENCES rarity (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_B64BF632861B2665 FOREIGN KEY (parent1_id) REFERENCES emoji (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_B64BF63294AE898B FOREIGN KEY (parent2_id) REFERENCES emoji (id) NOT DEFERRABLE INITIALLY IMMEDIATE)
SQL);
$this->addSql(<<<'SQL'
INSERT INTO emoji (id, rarity_id, name, code, intelligence, strength, toughness, speed, fights_won) SELECT id, rarity_id, name, code, intelligence, strength, toughness, speed, fights_won FROM __temp__emoji
SQL);
$this->addSql(<<<'SQL'
DROP TABLE __temp__emoji
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_B64BF632F3747573 ON emoji (rarity_id)
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_B64BF632861B2665 ON emoji (parent1_id)
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_B64BF63294AE898B ON emoji (parent2_id)
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
CREATE TEMPORARY TABLE __temp__emoji AS SELECT id, rarity_id, name, code, strength, toughness, intelligence, speed, fights_won FROM emoji
SQL);
$this->addSql(<<<'SQL'
DROP TABLE emoji
SQL);
$this->addSql(<<<'SQL'
CREATE TABLE emoji (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, rarity_id INTEGER NOT NULL, name VARCHAR(255) NOT NULL, code VARCHAR(255) NOT NULL, strength DOUBLE PRECISION NOT NULL, toughness DOUBLE PRECISION NOT NULL, intelligence DOUBLE PRECISION NOT NULL, speed DOUBLE PRECISION NOT NULL, fights_won INTEGER NOT NULL, CONSTRAINT FK_B64BF632F3747573 FOREIGN KEY (rarity_id) REFERENCES rarity (id) NOT DEFERRABLE INITIALLY IMMEDIATE)
SQL);
$this->addSql(<<<'SQL'
INSERT INTO emoji (id, rarity_id, name, code, strength, toughness, intelligence, speed, fights_won) SELECT id, rarity_id, name, code, strength, toughness, intelligence, speed, fights_won FROM __temp__emoji
SQL);
$this->addSql(<<<'SQL'
DROP TABLE __temp__emoji
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_B64BF632F3747573 ON emoji (rarity_id)
SQL);
}
}

@ -0,0 +1,90 @@
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f4f6f8;
color: #333;
margin: 2rem auto;
max-width: 800px;
padding: 1rem;
}
h1 {
color: #2c3e50;
font-size: 2rem;
margin-bottom: 1rem;
text-align: center;
}
h2 {
color: #34495e;
font-size: 1.4rem;
margin-top: 2rem;
margin-bottom: 1rem;
border-bottom: 2px solid #ccc;
padding-bottom: 0.2rem;
}
section {
background: white;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
margin-bottom: 1.5rem;
}
ul#user-list {
list-style: none;
padding: 0;
}
#user-list li {
background: #ecf0f1;
margin-bottom: 0.5rem;
padding: 0.8rem 1rem;
border-radius: 6px;
display: flex;
justify-content: space-between;
align-items: center;
}
#user-list li button {
background-color: #e74c3c;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
}
#user-list li button:hover {
background-color: #c0392b;
}
form#add-user-form {
display: flex;
flex-direction: column;
gap: 0.8rem;
}
form#add-user-form input,
form#add-user-form select {
padding: 0.6rem;
border: 1px solid #bdc3c7;
border-radius: 4px;
font-size: 1rem;
}
form#add-user-form button {
background-color: #3498db;
color: white;
border: none;
padding: 0.7rem;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
}
form#add-user-form button:hover {
background-color: #2980b9;
}

@ -4,6 +4,13 @@ body {
text-align: center; text-align: center;
} }
body, html {
margin: 0;
padding: 0;
height: 100%;
overflow-x: hidden;
}
h1 { h1 {
font-size: 3rem; font-size: 3rem;
margin-bottom: 30px; margin-bottom: 30px;
@ -11,12 +18,45 @@ h1 {
color: #f8b435; color: #f8b435;
} }
.emoji-container { .section-divider {
border: none;
border-top: 3px solid #f8b435;
margin: 50px auto 20px;
width: 80%;
}
.section-break-icon {
text-align: center;
font-size: 2rem;
margin: -20px 0;
color: #f8b435;
}
.section-title {
margin: 40px 10px;
font-size: 1.8rem;
color: #f8f5e0;
}
.emoji-container:not(.base) {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 30px;
width: fit-content;
margin: 0 auto;
}
.emoji-container.base {
display: flex; display: flex;
justify-content: center; justify-content: center;
flex-wrap: wrap; flex-wrap: wrap;
gap: 30px; gap: 30px;
margin-bottom: 10rem;
border: 3px dashed #f8b435;
padding: 20px;
margin: 0 auto 30px auto;
border-radius: 15px;
width: fit-content;
} }
.emoji-card { .emoji-card {
@ -43,7 +83,7 @@ h1 {
/* Animation brillance pour rareté légendaire */ /* Animation brillance pour rareté légendaire */
@keyframes shine { @keyframes shine {
0% { background-position: 0px; } 0% { background-position: 0px; }
100% { background-position: 177px; } 100% { background-position: 168px; }
} }
.emoji-card.gold { .emoji-card.gold {
@ -54,17 +94,17 @@ h1 {
.emoji-card.gold::before { .emoji-card.gold::before {
content: ''; content: '';
position: absolute; position: absolute;
top: 0; top: 1%;
left: 1%; left: 1%;
width: 98%; width: 97%;
height: 100%; height: 99%;
background: linear-gradient( background: linear-gradient(
120deg, 120deg,
rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.5) 50%, rgba(255, 255, 255, 0.8) 50%,
rgba(255, 255, 255, 0) 100% rgba(255, 255, 255, 0) 100%
); );
animation: shine 5s infinite; animation: shine 3.5s infinite;
pointer-events: none; pointer-events: none;
z-index: -1; z-index: -1;
} }
@ -118,6 +158,12 @@ h1 {
margin-bottom: 10px; margin-bottom: 10px;
} }
.emoji img {
vertical-align: middle;
width: 68px;
height: 68px;
}
.emoji-name { .emoji-name {
font-weight: bold; font-weight: bold;
font-size: 1.2rem; font-size: 1.2rem;
@ -156,10 +202,16 @@ h1 {
background-color: #e5d6b8; background-color: #e5d6b8;
} }
.detail-icon {
.btn-logout {
width: 238px;
height: 50px;
}
.detail-icon-hierarchy {
position: absolute; position: absolute;
top: 8px; top: 8px;
right: 10px; left: 10px;
cursor: pointer; cursor: pointer;
font-size: 18px; font-size: 18px;
color: #555; color: #555;
@ -182,6 +234,112 @@ color: #555;
font-size: 14px; font-size: 14px;
} }
/* Partie Hierachie*/
.hierarchy-modal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: none; /* sera activé par JS */
z-index: 1000;
}
.hierarchy-overlay {
position: fixed; /* ← Important ! */
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0,0,0,0.6);
z-index: 1001;
}
.hierarchy-content {
position: fixed; /* ← Pour rester centré par rapport au viewport */
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 12px;
z-index: 1002;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
max-width: 90%;
width: 500px;
}
.close-btn {
position: absolute;
top: 10px;
right: 20px;
font-size: 30px;
cursor: pointer;
}
.hierarchy-graph {
display: grid;
grid-template-columns: 1fr auto 1fr;
grid-template-rows: auto auto;
grid-template-areas:
"parent1 arrows parent2"
". child .";
gap: 10px 30px;
align-items: center;
justify-items: center;
margin-top: 30px;
}
.child-block {
grid-area: child;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.parent-block {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.parent-block .emoji {
font-size: 40px;
margin-bottom: 5px;
}
.parent-block .name {
font-size: 14px;
color: #444;
font-style: italic;
}
.arrows {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.arrow {
font-size: 24px;
margin: 10px 0;
}
.child {
grid-area: child;
font-size: 50px;
padding: 10px;
}
.connector {
grid-column: 2;
font-size: 30px;
color: #444;
}
/* Filtre et Tri*/ /* Filtre et Tri*/
.filter-bar { .filter-bar {
margin-bottom: 30px; margin-bottom: 30px;
@ -219,7 +377,7 @@ color: #555;
#selection-status { #selection-status {
font-size: 1.1rem; font-size: 1.1rem;
margin-bottom: 20px; margin-top: 4rem;
font-weight: bold; font-weight: bold;
color: #f9e8c0; color: #f9e8c0;
} }
@ -246,3 +404,36 @@ color: #555;
font-size: 1.5rem; font-size: 1.5rem;
font-weight: bold; font-weight: bold;
} }
/* Pagination */
.pagination-bar {
margin: 4rem 4rem;
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
color: #f8f5e0;
font-family: 'Georgia', serif;
}
.pagination-bar button {
background: none;
border: none;
font-size: 1.1rem;
color: #f8f5e0;
cursor: pointer;
padding: 5px 10px;
border-radius: 50%;
transition: background 0.2s ease;
}
.pagination-bar button.active {
background: #333;
color: white;
font-weight: bold;
}
.pagination-bar button:hover:not(.active) {
color: #f8b435;
}

@ -0,0 +1,118 @@
body {
background-color: #314e57;
font-family: 'Georgia', serif;
text-align: center;
}
.login-container {
background-color: #3b6068;
border: 4px solid #000;
border-radius: 15px;
padding: 40px 30px;
margin: 60px auto;
width: 90%;
max-width: 400px;
box-shadow: 0 0 30px rgba(0,0,0,0.6);
color: #f8f5e0;
font-family: 'Georgia', serif;
}
.login-title {
font-size: 2.5rem;
color: #f8b435;
margin-bottom: 20px;
text-shadow: 2px 2px 4px #000;
}
.login-error {
background-color: #8b0000;
color: #fff8e1;
padding: 10px;
border: 2px solid #000;
border-radius: 8px;
margin-bottom: 20px;
font-weight: bold;
}
.already-logged {
background-color: #f2e6c9;
color: #000;
padding: 12px;
border: 2px dashed #000;
border-radius: 8px;
margin-bottom: 20px;
font-size: 0.95rem;
}
.logout-link {
display: inline-block;
margin-top: 10px;
color: #8b0000;
font-weight: bold;
text-decoration: underline;
}
.login-form {
display: flex;
flex-direction: column;
gap: 15px;
}
.login-form label {
text-align: left;
color: #f8f5e0;
font-weight: bold;
}
.login-form input[type="text"],
.login-form input[type="password"] {
padding: 10px;
border: 2px solid #000;
border-radius: 6px;
background-color: #f2e6c9;
font-family: 'Georgia', serif;
font-size: 1rem;
box-shadow: inset 2px 2px 5px rgba(0,0,0,0.2);
}
.btn-login {
background-color: #f8b435;
color: #000;
font-weight: bold;
border: 3px solid #000;
padding: 10px;
border-radius: 8px;
font-size: 1.2rem;
cursor: pointer;
box-shadow: 3px 3px 0 #000;
transition: transform 0.2s ease;
}
.btn-login:hover {
transform: scale(1.05);
background-color: #e09f30;
}
.no-account {
margin-top: 30px;
color: #f8f5e0;
}
.btn-register {
display: inline-block;
margin-top: 10px;
background-color: #f2e6c9;
color: #000;
border: 2px solid #000;
padding: 8px 20px;
border-radius: 6px;
text-decoration: none;
font-weight: bold;
box-shadow: 2px 2px 0 #000;
transition: transform 0.2s ease;
}
.btn-register:hover {
transform: scale(1.05);
background-color: #e5d6b8;
}

@ -0,0 +1,88 @@
body {
background-color: #314e57 !important;
font-family: 'Georgia', serif;
text-align: center;
}
.register-container {
background-color: #3b6068;
border: 4px solid #000;
border-radius: 15px;
padding: 40px 30px;
margin: 60px auto;
width: 90%;
max-width: 400px;
box-shadow: 0 0 30px rgba(0,0,0,0.6);
color: #f8f5e0;
font-family: 'Georgia', serif;
}
.register-title {
font-size: 2.5rem;
color: #f8b435;
margin-bottom: 20px;
text-shadow: 2px 2px 4px #000;
}
.register-form label {
text-align: left;
color: #f8f5e0;
font-weight: bold;
}
.register-form input[type="text"],
.register-form input[type="password"] {
padding: 10px;
border: 2px solid #000;
border-radius: 6px;
background-color: #f2e6c9;
font-family: 'Georgia', serif;
font-size: 1rem;
box-shadow: inset 2px 2px 5px rgba(0,0,0,0.2);
width: 100%;
margin-bottom: 15px;
}
.btn-register-submit {
background-color: #f8b435;
color: #000;
font-weight: bold;
border: 3px solid #000;
padding: 10px;
border-radius: 8px;
font-size: 1.2rem;
cursor: pointer;
box-shadow: 3px 3px 0 #000;
transition: transform 0.2s ease;
width: 100%;
margin-bottom: 20px;
}
.btn-register-submit:hover {
transform: scale(1.05);
background-color: #e09f30;
}
.already-account {
color: #f8f5e0;
font-size: 0.95rem;
}
.btn-login-link {
display: inline-block;
margin-top: 10px;
background-color: #f2e6c9;
color: #000;
border: 2px solid #000;
padding: 8px 20px;
border-radius: 6px;
text-decoration: none;
font-weight: bold;
box-shadow: 2px 2px 0 #000;
transition: transform 0.2s ease;
}
.btn-login-link:hover {
transform: scale(1.05);
background-color: #e5d6b8;
}

@ -1,5 +1,7 @@
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// Copie tout ici
let selectedCards = []; let selectedCards = [];
function toggleSelection(card) { function toggleSelection(card) {
@ -40,22 +42,33 @@ document.addEventListener('DOMContentLoaded', () => {
} }
} }
function handleAction(type) { async function handleAction(type) {
if (selectedCards.length !== 2) { if (selectedCards.length !== 2) {
alert("Tu dois sélectionner 2 créatures."); alert("Tu dois sélectionner 2 créatures.");
return; return;
} }
const name1 = selectedCards[0].dataset.name; const id1 = selectedCards[0].dataset.id;
const name2 = selectedCards[1].dataset.name; const id2 = selectedCards[1].dataset.id;
const idUser = document.getElementById('user-data').dataset.userId;
if (type === 'reproduction') {
window.location.href = `https://localhost:8000/reproduction/${encodeURIComponent(idUser)}/${encodeURIComponent(id1)}/${encodeURIComponent(id2)}`;
return; // on quitte ici
}
if (type === 'combat') { if (type === 'combat') {
console.log(`Combat : ${name1} contre ${name2}`);
} else if (type === 'reproduction') { window.location.href = `https://localhost:8000/combat/${encodeURIComponent(id1)}/${encodeURIComponent(id2)}`;
console.log(`Accouplement : ${name1} et ${name2}`);
return; // on quitte ici
} }
// Réinitialiser après l'action // Réinitialiser si pas redirection
selectedCards.forEach(card => card.classList.remove('selected')); selectedCards.forEach(card => card.classList.remove('selected'));
selectedCards = []; selectedCards = [];
updateSelectionDisplay(); updateSelectionDisplay();
@ -64,8 +77,67 @@ document.addEventListener('DOMContentLoaded', () => {
// Ouvre / Ferme la popup d'information // Ouvre / Ferme la popup d'information
function togglePopup(id) { function togglePopup(id) {
const popup = document.getElementById('popup-' + id); const popup = document.getElementById('popup-' + id);
if (popup) {
popup.style.display = (popup.style.display === 'block') ? 'none' : 'block'; popup.style.display = (popup.style.display === 'block') ? 'none' : 'block';
} }
}
// Ouvre / Ferme la popup de hierachie de famille
async function toggleHierachiPopup(id) {
try {
const response = await fetch(`/emojis/${id}/getParents/`, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
if (!response.ok) throw new Error("Erreur serveur : " + response.status);
const data = await response.json();
// Injection dynamique dans la popup
const parent1El = document.getElementById(`parent1-${id}`);
if (data.codeParent1 && data.codeParent1.includes('vercel')) {
parent1El.innerHTML = `<img src="${data.codeParent1}" alt="Parent 1" width="48" height="48">`;
} else {
parent1El.innerText = data.codeParent1 || '❓';
}
const parent2El = document.getElementById(`parent2-${id}`);
if (data.codeParent2 && data.codeParent2.includes('vercel')) {
parent2El.innerHTML = `<img src="${data.codeParent2}" alt="Parent 2" width="48" height="48">`;
} else {
parent2El.innerText = data.codeParent2 || '❓';
}
const childEl = document.getElementById(`child-${id}`);
if (data.codeEnfant && data.codeEnfant.includes('vercel')) {
childEl.innerHTML = `<img src="${data.codeEnfant}" alt="Emoji fusionné" width="64" height="64">`;
} else {
childEl.innerText = data.codeEnfant || '❓';
}
document.getElementById(`name-parent1-${id}`).innerText = data.nameParent1 || 'inconnu';
document.getElementById(`name-parent2-${id}`).innerText = data.nameParent2 || 'inconnu';
document.getElementById(`name-child-${id}`).innerText = data.nameEnfant || 'inconnu';
console.log("Parents récupérés :", data);
// Affichage plein écran
const popup = document.getElementById(`popup-hiera-${id}`);
if (popup) popup.style.display = 'block';
} catch (error) {
console.error("Erreur lors de la récupération des parents :", error);
alert("Une erreur est survenue lors de l'affichage de la hiérarchie.");
}
}
function closeHierarchyPopup(id) {
const popup = document.getElementById(`popup-hiera-${id}`);
if (popup) popup.style.display = 'none';
}
// Fermer les autres popups en cliquant ailleurs // Fermer les autres popups en cliquant ailleurs
document.addEventListener('click', function(e) { document.addEventListener('click', function(e) {
@ -74,6 +146,16 @@ document.addEventListener('DOMContentLoaded', () => {
p.style.display = 'none'; p.style.display = 'none';
} }
}); });
// Partie pour fermer la popup de hiérarchie
document.querySelectorAll('.close-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const id = btn.dataset.id; // On récupère l'id depuis l'attribut data-id
const popup = document.getElementById(`popup-hiera-${id}`);
if (popup) popup.style.display = 'none';
e.stopPropagation(); // Empêche la propagation du clic
});
});
}); });
// Fonction pour appliquer la recherche, les filtres et le tri // Fonction pour appliquer la recherche, les filtres et le tri
@ -82,7 +164,7 @@ document.addEventListener('DOMContentLoaded', () => {
const sortBy = document.getElementById('sort-select').value; const sortBy = document.getElementById('sort-select').value;
const searchTerm = document.getElementById('search-input').value.toLowerCase(); const searchTerm = document.getElementById('search-input').value.toLowerCase();
const cards = Array.from(document.querySelectorAll('.emoji-card')); const cards = Array.from(document.querySelectorAll('#collection-container .emoji-card'));
cards.forEach(card => { cards.forEach(card => {
const color = card.dataset.color; const color = card.dataset.color;
@ -99,7 +181,7 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
if (sortBy !== 'none') { if (sortBy !== 'none') {
const container = document.querySelector('.emoji-container'); const container = document.getElementById('collection-container');
const visibleCards = cards.filter(c => c.style.display !== 'none'); const visibleCards = cards.filter(c => c.style.display !== 'none');
visibleCards.sort((a, b) => { visibleCards.sort((a, b) => {
@ -109,21 +191,41 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
visibleCards.forEach(card => container.appendChild(card)); visibleCards.forEach(card => container.appendChild(card));
paginateCards();
} }
} }
// Appel Fonctionnalité de popup d'information // Appel Fonctionnalité de popup d'information
document.querySelectorAll('.detail-icon').forEach(icon => { document.querySelectorAll('.detail-icon').forEach(icon => {
icon.addEventListener('click', (e) => { icon.addEventListener('click', (e) => {
const id = icon.parentElement.dataset.id; const card = icon.closest('.emoji-card');
const id = card?.dataset.id;
if (id) {
togglePopup(id); togglePopup(id);
e.stopPropagation(); // empêche le clic daller à la carte }
e.stopPropagation(); // évite que ça sélectionne la carte
});
});
// Appel Fonctionnalité de popup de hierachie de famille
document.querySelectorAll('.detail-icon-hierarchy').forEach(icon => {
icon.addEventListener('click', (e) => {
const card = icon.closest('.emoji-card');
const id = card?.dataset.id;
if (id) {
toggleHierachiPopup(id);
}
e.stopPropagation(); // évite que ça sélectionne la carte
}); });
}); });
// Appel Fonctionnalité de sélection des cartes // Appliquer l'écouteur à toutes les cartes (base + collection)
document.querySelectorAll('.emoji-card').forEach(card => { document.querySelectorAll('.emoji-container').forEach(container => {
card.addEventListener('click', () => toggleSelection(card)); container.addEventListener('click', (e) => {
const card = e.target.closest('.emoji-card');
if (!card || e.target.closest('.detail-icon')) return; // Ignore si clic sur icône
toggleSelection(card);
});
}); });
// Appel Fonctionnalité de combat et reproduction // Appel Fonctionnalité de combat et reproduction
@ -138,4 +240,66 @@ document.addEventListener('DOMContentLoaded', () => {
document.getElementById('search-input').addEventListener('input', applyFilters); document.getElementById('search-input').addEventListener('input', applyFilters);
document.getElementById('rarete-filter').addEventListener('change', applyFilters); document.getElementById('rarete-filter').addEventListener('change', applyFilters);
document.getElementById('sort-select').addEventListener('change', applyFilters); document.getElementById('sort-select').addEventListener('change', applyFilters);
/***** Partie Pagination ******/
const ITEMS_PER_PAGE = 10;
let currentPage = 1;
function paginateCards() {
const allCards = Array.from(document.querySelectorAll('#collection-container .emoji-card'));
const container = document.getElementById('collection-container');
const totalPages = Math.ceil(allCards.length / ITEMS_PER_PAGE);
// Masquer toutes les cartes
allCards.forEach(card => card.style.display = 'none');
// Afficher uniquement les cartes de la page courante
const start = (currentPage - 1) * ITEMS_PER_PAGE;
const end = start + ITEMS_PER_PAGE;
allCards.slice(start, end).forEach(card => card.style.display = 'block');
renderPagination(totalPages);
}
function renderPagination(totalPages) {
const pagination = document.getElementById('pagination');
pagination.innerHTML = '';
const addBtn = (text, page = null, isActive = false, disabled = false) => {
const btn = document.createElement('button');
btn.textContent = text;
if (isActive) btn.classList.add('active');
if (disabled) btn.disabled = true;
if (page !== null) {
btn.addEventListener('click', () => {
currentPage = page;
paginateCards();
});
}
pagination.appendChild(btn);
};
addBtn('Précédent', currentPage - 1, false, currentPage === 1);
for (let i = 1; i <= totalPages; i++) {
if (
i === 1 ||
i === totalPages ||
(i >= currentPage - 1 && i <= currentPage + 1)
) {
addBtn(i, i, i === currentPage);
} else if (
i === 2 && currentPage > 3 ||
i === totalPages - 1 && currentPage < totalPages - 2
) {
addBtn('...');
}
}
addBtn('Suivant', currentPage + 1, false, currentPage === totalPages);
}
paginateCards();
}); });

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

@ -0,0 +1,65 @@
document.addEventListener('DOMContentLoaded', () => {
const left = document.querySelector('.left-emoji');
const right = document.querySelector('.right-emoji');
const winnerText = document.getElementById('winner-text');
fetch(`https://localhost:8000/emojis/fight/${id1}/${id2}`)
.then(response => response.json())
.then(data => {
const { leftEmoji, rightEmoji, winner } = data;
left.dataset.content = leftEmoji;
right.dataset.content = rightEmoji;
window.winnerSide = winner === 'left' ? 'left' : 'right';
window.loserSide = winner === 'left' ? 'right' : 'left';
insertEmojiOrImage(left);
insertEmojiOrImage(right);
left.classList.add('slide-in-left');
right.classList.add('slide-in-right');
setTimeout(() => {
left.style.left = '100px';
right.style.left = '300px';
left.classList.add('fight-animation');
right.classList.add('fight-animation');
setTimeout(() => {
left.classList.remove('fight-animation');
right.classList.remove('fight-animation');
const loser = window.loserSide === 'left' ? left : right;
loser.classList.add('loser-fly-up');
const winnerEl = window.winnerSide === 'left' ? left : right;
winnerEl.classList.add('winner-center', 'winner-crown');
let name = winnerEl.dataset.content;
if (/^https?:\/\//.test(name)) {
winnerText.innerHTML = `<img src="${name}" style="width:64px">a gagné ! 🎉`;
} else {
winnerText.textContent = `${name} a gagné ! 🎉`;
}
winnerText.style.display = 'block';
}, 1500);
}, 3000);
});
function insertEmojiOrImage(el) {
const value = el.dataset.content;
if (/^https?:\/\//.test(value)) {
const img = document.createElement('img');
img.src = value;
img.className = 'emoji-img';
el.appendChild(img);
} else {
el.textContent = value;
}
}
document.getElementById('btn-retour').addEventListener('click', () => {
window.location.href = `https://localhost:8000/`;
});
});

@ -0,0 +1,67 @@
document.addEventListener('DOMContentLoaded', () => {
const left = document.querySelector('.left-emoji');
const right = document.querySelector('.right-emoji');
const childText = document.getElementById('child-text');
const heartGif = document.getElementById('heart-gif');
fetch(`https://localhost:8000/emojis/fusion/${id_user}/${id_left}/${id_right}`)
.then(response => response.json())
.then(data => {
child_emoji=data['baby'];
left.dataset.content=data['mommy'];
right.dataset.content=data['daddy'];
function insertEmojiOrImage(el) {
const value = el.dataset.content;
console.log(value)
if (/^https?:\/\//.test(value)) {
const img = document.createElement('img');
img.src = value;
img.className = 'emoji-img';
el.appendChild(img);
} else {
el.textContent = value;
}
}
insertEmojiOrImage(left);
insertEmojiOrImage(right);
left.classList.add('slide-in-left');
right.classList.add('slide-in-right');
setTimeout(() => {
if (/^https?:\/\//.test(child_emoji)) {
emojiHtml = `
<div class="emoji-glow-img">
<img src="${child_emoji}" style="width:64px; height:64px">
</div>`;
} else {
emojiHtml = `
<span class="emoji-glow-text" data-emoji="${child_emoji}">${child_emoji}</span>`;
}
childText.innerHTML = `
<div class="emoji-wrapper">
${emojiHtml}
</div>`;
childText.classList.add('growing-animation');
childText.style.display = 'block';
heartGif.style.display = 'block';
heartGif.classList.add('fade-in');
}, 3000);
})
.catch(error => {
console.error('Erreur:', error);
});
document.getElementById('btn-retour').addEventListener('click', () => {
window.location.href = `https://localhost:8000/`;
});
});

@ -0,0 +1,157 @@
body {
margin: 0;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-image: url("../media/boxing_ring.jpg");
background-position: center;
background-repeat: no-repeat;
background-size: cover;
overflow: hidden;
font-family: Arial, sans-serif;
}
.arena {
position: relative;
width: 400px;
height: 150px;
}
.emoji {
position: absolute;
top: 50%;
font-size: 5rem;
transform: translate(-50%, -50%);
}
.emoji-img {
width: 5rem;
height: 5rem;
object-fit: contain;
}
.winner-center .emoji-img {
width: 6rem;
height: 6rem;
}
.loser-fly-up .emoji-img {
opacity: 0.6;
}
.left-emoji {
left: -100px;
}
.right-emoji {
left: 500px;
}
@keyframes slideInLeft {
to {
left: 100px;
}
}
@keyframes slideInRight {
to {
left: 300px;
}
}
.slide-in-left {
animation: slideInLeft 3s forwards;
}
.slide-in-right {
animation: slideInRight 3s forwards;
}
@keyframes fightShake {
0%,
100% {
transform: translate(-50%, -50%) translateX(0);
}
20%,
60% {
transform: translate(-50%, -50%) translateX(-15px);
}
40%,
80% {
transform: translate(-50%, -50%) translateX(15px);
}
}
.fight-animation {
animation: fightShake 0.5s 3;
}
@keyframes flyUpFade {
to {
opacity: 0;
transform: translate(-50%, calc(-150px - 50%));
}
}
.loser-fly-up {
animation: flyUpFade 3s forwards;
position: absolute;
}
@keyframes moveToCenter {
to {
left: 50%;
transform: translate(-50%, -50%);
}
}
.winner-center {
animation: moveToCenter 1s forwards;
position: absolute !important;
top: 50% !important;
font-size: 6rem;
z-index: 10;
}
.winner-crown::after {
content: "👑";
position: absolute;
top: -1.5rem;
left: 50%;
transform: translateX(-50%);
font-size: 2rem;
}
#winner-text {
margin-top: 1rem;
font-size: 2rem;
text-align: center;
display: none;
}
/* Bouton de retour */
.btn-retour {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
padding: 10px 25px;
font-size: 1.1rem;
font-weight: bold;
background-color: #f2e6c9;
border: 2px solid #000;
border-radius: 8px;
cursor: pointer;
box-shadow: 2px 2px 0 #000;
transition: transform 0.2s ease;
z-index: 9999; /* pour quil soit toujours au-dessus */
}
.btn-retour:hover {
transform: translateX(-50%) scale(1.05);
background-color: #e0d1b3;
}

@ -0,0 +1,179 @@
body {
margin: 0;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-image: url("../media/champ_amour.webp");
background-position: center;
background-repeat: no-repeat;
background-size: cover;
overflow: hidden;
font-family: Arial, sans-serif;
}
.field {
position: relative;
width: 400px;
height: 150px;
}
.emoji {
position: absolute;
top: 50%;
font-size: 5rem;
transform: translate(-50%, -50%);
}
.emoji-img {
width: 5rem;
height: 5rem;
object-fit: contain;
}
.left-emoji {
left: -100px;
}
.right-emoji {
left: 500px;
}
@keyframes slideInLeft {
to {
left: 200px;
opacity:0;
}
}
@keyframes slideInRight {
to {
left: 200px;
opacity:0;
}
}
.slide-in-left {
animation: slideInLeft 3s forwards;
}
.slide-in-right {
animation: slideInRight 3s forwards;
}
@keyframes Fade {
to {
opacity: 0;
}
}
.fading-animation {
animation: Fade 3s forwards;
}
@keyframes growing {
to {
transform: scale(5);
}
}
.growing-animation {
animation: growing 5s forwards;
}
#child-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
justify-content: center;
align-items: center;
text-align: center;
flex-direction: column;
}
.emoji-wrapper {
display: flex;
flex-direction: column;
align-items: center;
}
.emoji-glow-text {
position: relative;
display: inline-block;
font-size: 5rem;
}
.emoji-glow-text::before {
content: attr(data-emoji);
position: absolute;
top: 0;
left: 0;
filter: blur(1px) brightness(10);
opacity: 0.9;
z-index: -1;
}
.emoji-glow-img {
position: relative;
display: inline-block;
}
.emoji-glow-img::before {
content: "";
position: absolute;
top: 0;
left: 0;
filter: blur(1px) brightness(10);
opacity: 0.9;
z-index: -1;
}
.birth-text{
font-size: 2rem;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
margin-top: 20px;
}
#heart-gif{
position: absolute;
display:none;
background-image: url("../media/hearts.gif");
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 30vw;
height: 50vh;
background-repeat: no-repeat;
background-size: 100% 100%;
z-index: -1;
}
/* Bouton de retour */
.btn-retour {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
padding: 10px 25px;
font-size: 1.1rem;
font-weight: bold;
background-color: #f2e6c9;
border: 2px solid #000;
border-radius: 8px;
cursor: pointer;
box-shadow: 2px 2px 0 #000;
transition: transform 0.2s ease;
z-index: 9999; /* pour quil soit toujours au-dessus */
}
.btn-retour:hover {
transform: translateX(-50%) scale(1.05);
background-color: #e0d1b3;
}

@ -0,0 +1,78 @@
<?php
namespace App\Command;
use Doctrine\DBAL\Connection;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class PopulateDBEmojiAvailableCommand extends Command
{
protected static $defaultName = 'app:popDBEmojiAvai';
private Connection $connection;
public function __construct(Connection $connection)
{
parent::__construct();
$this->connection = $connection;
}
protected function configure()
{
$this
->setDescription('Populate the database.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
try {
// On supprime la table si elle existe déjà
$this->connection->executeStatement('DROP TABLE IF EXISTS stock_emoji');
// On crée la table
$this->connection->executeStatement('
CREATE TABLE stock_emoji (
id INT PRIMARY KEY,
code VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL
)
');
// On peuple la table
$this->connection->executeStatement("INSERT INTO stock_emoji (id, code, name) VALUES
(1,'🤖','Rodolph'),
(2,'😺','Bobette'),
(3,'🧠','Diana'),
(4,'👻','Ian'),
(5,'🧟','Alice'),
(6,'🐶','Eric'),
(7,'👽','Hannah'),
(8,'🧛','Fiona'),
(9,'🎃','George'),
(10,'🐸','John'),
(11,'⚡','Charlie'),
(12,'💀','Benoit'),
(13,'🔥','Sophie'),
(14,'🧙','Bob'),
(15,'🌪️','Ethan'),
(16,'😎','Luna'),
(17,'😁','Maxence'),
(18,'🌟','Jasper'),
(19,'😈','Nora')
");
$output->writeln('Base de données peuplée.');
} catch (\Exception $e) {
$output->writeln('<error>Erreur : ' . $e->getMessage() . '</error>');
return Command::FAILURE;
}
return Command::SUCCESS;
}
}

@ -0,0 +1,98 @@
<?php
namespace App\Controller;
use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
#[Route('/admin', name: 'admin_')]
#[IsGranted('ROLE_ADMIN')]
class AdminController extends AbstractController
{
private EntityManagerInterface $entityManager;
private UserPasswordHasherInterface $passwordHasher;
public function __construct(EntityManagerInterface $entityManager, UserPasswordHasherInterface $passwordHasher)
{
$this->entityManager = $entityManager;
$this->passwordHasher = $passwordHasher;
}
#[Route('', name: 'admin_dashboard')]
public function dashboard(): Response
{
return $this->render('admin/index.html.twig');
}
#[Route('/users', name: 'users_list', methods: ['GET'])]
public function getUserById(UserRepository $userRepository): JsonResponse
{
$users = $userRepository->findAll();
$data = array_map(function (User $user) {
return [
'id' => $user->getId(),
'username' => $user->getUsername(),
'roles' => $user->getRoles(),
];
}, $users);
return $this->json($data);
}
#[Route('/users/add', name: 'add_user', methods: ['POST'])]
public function addUser(Request $request, UserPasswordHasherInterface $passwordHasher): JsonResponse
{
$data = json_decode($request->getContent(), true);
$username = $data['username'] ?? null;
$password = $data['password'] ?? null;
$roles = $data['roles'] ?? ['ROLE_USER'];
if (!$username || !$password) {
return $this->json(['error' => 'Missing username or password'], Response::HTTP_BAD_REQUEST);
}
$existingUser = $this->entityManager->getRepository(User::class)->findOneBy(['username' => $username]);
if ($existingUser) {
return $this->json(['error' => 'User already exists'], Response::HTTP_CONFLICT);
}
$user = new User();
$user->setUsername($username);
$user->setRoles($roles);
$user->setPassword($passwordHasher->hashPassword($user, $password));
$this->entityManager->persist($user);
$this->entityManager->flush();
return $this->json(['message' => 'User created successfully', 'id' => $user->getId()], Response::HTTP_CREATED);
}
#[Route('/users/delete/{id}', name: 'delete_user', methods: ['DELETE'])]
public function deleteUser(int $id): JsonResponse
{
$user = $this->entityManager->getRepository(User::class)->find($id);
if (!$user) {
return $this->json(['error' => 'User not found'], Response::HTTP_NOT_FOUND);
}
foreach ($user->getEmojis() as $userEmoji) {
$this->entityManager->remove($userEmoji);
}
$this->entityManager->remove($user);
$this->entityManager->flush();
return $this->json(['message' => 'User and related data deleted successfully'], Response::HTTP_OK);
}
}

@ -0,0 +1,45 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class CombatController extends AbstractController
{
// Route designed for testing purposes
#[Route('/combat', name: 'app_combat')]
public function index(): Response
{
$leftEmoji = '🐱';
$rightEmoji = '🐶';
$winner = 'left'; // or 'right'
$loser = ($winner === 'left') ? 'right' : 'left';
return $this->render('combat/index.html.twig', [
'left_emoji' => $leftEmoji,
'right_emoji' => $rightEmoji,
'winner' => $winner,
'loser' => $loser,
]);
}
private function isEmoji(string $str): bool {
$emojiRegex = '/^\X$/u';
return preg_match($emojiRegex, $str) === 1 && mb_strlen($str, 'UTF-8') === 1;
}
#[Route('/combat/{idLeft}/{idRight}', name: 'app_combat_with_params', requirements: ['leftEmoji' => '.+', 'rightEmoji' => '.+'])]
public function combatWithParams(string $idLeft, string $idRight): Response
// Example : /combat-🐱-🐶-left
// or /combat-🐌_☀%EF%B8%8F-🐌_☀%EF%B8%8F-left
{
return $this->render('combat/index.html.twig', [
'idLeft' => $idLeft,
'idRight' => $idRight,
]);
}
}

@ -3,6 +3,7 @@ namespace App\Controller;
use App\Entity\Emoji; use App\Entity\Emoji;
use App\Entity\Rarity; use App\Entity\Rarity;
use App\Service\EmojiService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@ -12,20 +13,33 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use App\Repository\EmojiRepository; use App\Repository\EmojiRepository;
use App\Entity\UserEmojis;
use App\Repository\UserRepository;
use App\Repository\UserEmojisRepository;
use App\Entity\StockEmoji;
#[Route('/emoji', name: 'emoji')] #[Route('/emojis', name: 'app_emoji_')]
class EmojiController extends AbstractController class EmojiController extends AbstractController
{ {
private RarityRepository $rarityRepository; private RarityRepository $rarityRepository;
private HttpClientInterface $httpClient; private HttpClientInterface $httpClient;
private EmojiService $emojiService;
private UserRepository $userRepository;
private UserEmojisRepository $userEmojisRepository;
private EntityManagerInterface $em;
public function __construct(RarityRepository $rarityRepository, HttpClientInterface $httpClient) public function __construct(RarityRepository $rarityRepository, HttpClientInterface $httpClient, EmojiService $emojiService,
UserRepository $userRepository, UserEmojisRepository $userEmojisRepository, EntityManagerInterface $em)
{ {
$this->rarityRepository = $rarityRepository; $this->rarityRepository = $rarityRepository;
$this->httpClient = $httpClient; $this->httpClient = $httpClient;
$this->emojiService = $emojiService;
$this->userRepository = $userRepository;
$this->userEmojisRepository = $userEmojisRepository;
$this->em = $em;
} }
#[Route('/emoji', name: 'app_emoji')] #[Route('/', name: 'emojis')]
public function index(): Response public function index(): Response
{ {
return $this->render('emoji/index.html.twig', [ return $this->render('emoji/index.html.twig', [
@ -33,49 +47,6 @@ class EmojiController extends AbstractController
]); ]);
} }
#[Route('/add/{code}', name: 'add')]
public function addEmojiDebug(string $code, EntityManagerInterface $entityManager) {
$emoji = new Emoji();
$emoji->setCode($code);
$emoji->setName('Default Name');
$emoji->setStrength(1.0);
$emoji->setToughness(1.0);
$emoji->setIntelligence(1.0);
$emoji->setSpeed(1.0);
$emoji->setFightsWon(0);
// On récupère une instance de Rarity existante (par exemple, la première)
$rarity = $this->getRarity();
if (!$rarity) {
throw new \RuntimeException('Aucun objet Rarity trouvé en base.');
}
$emoji->setRarity($rarity);
// Optionnel : définir parent1 et parent2 si tu veux tester avec des relations
// $emoji->setParent1(null);
// $emoji->setParent2(null);
$entityManager->persist($emoji);
$entityManager->flush();
return new Response();
}
private function getRarity(): Rarity {
$rarity = $this->rarityRepository->findAll();
$rand = mt_rand() / mt_getrandmax();
$sum = 0.0;
foreach($rarity as $r) {
$sum += $r->getDropRate();
if($sum > $rand) {
return $r;
}
}
return $rarity[0];
}
// Renvoi l'url de l'image issue de la fusion de deux emojis. Fonctionne avec un code et un emoji directement // Renvoi l'url de l'image issue de la fusion de deux emojis. Fonctionne avec un code et un emoji directement
private function getFusionUrl(string $emoji1, string $emoji2): string private function getFusionUrl(string $emoji1, string $emoji2): string
{ {
@ -89,8 +60,15 @@ class EmojiController extends AbstractController
return sprintf('%s%s_%s?size=%d', $baseUrl, $encodedEmoji1, $encodedEmoji2, $size); return sprintf('%s%s_%s?size=%d', $baseUrl, $encodedEmoji1, $encodedEmoji2, $size);
} }
#[Route('/fusion/{emoji1_id}/{emoji2_id}', name: 'fusion')] #[Route('/fusion/{userId}/{emoji1_id}/{emoji2_id}', name: 'fusion', methods: ['GET'], requirements: ['userId' => '\d+', 'emoji1_id' => '\d+', 'emoji2_id' => '\d+'])]
public function reproduceEmoji(int $emoji1_id, int $emoji2_id, EntityManagerInterface $entityManager, EmojiRepository $emojiRepository): JsonResponse { public function reproduceEmoji(int $userId, int $emoji1_id, int $emoji2_id, EntityManagerInterface $entityManager, EmojiRepository $emojiRepository): JsonResponse {
$user = $this->userRepository->findOneBy(['id' => $userId]);
if (!$user) {
return new JsonResponse(['error' => 'User not found'], 404);
}
$emoji1 = $emojiRepository->find($emoji1_id); $emoji1 = $emojiRepository->find($emoji1_id);
$emoji2 = $emojiRepository->find($emoji2_id); $emoji2 = $emojiRepository->find($emoji2_id);
@ -100,11 +78,24 @@ class EmojiController extends AbstractController
$child = new Emoji(); $child = new Emoji();
$child->setName("Testenfant"); $names = [
'Zabbo', 'Snorky', 'Wiblo', 'Kipzi', 'Gribz', 'Flimbo', 'Plinko', 'Raffu', 'Mibzo', 'Dazzo',
'Flinko', 'Groopi', 'Zimba', 'Wocky', 'Trizzi', 'Lumpo', 'Yabbo', 'Glinky', 'Zuffo', 'Brimbo',
'Chompa', 'Snizzle', 'Wibzi', 'Paffy', 'Tizzle', 'Nokko', 'Lazzo', 'Flumzi', 'Wozzy', 'Blimpo',
'Zuppi', 'Glinty', 'Fobzi', 'Muggo', 'Twibzy', 'Crungo', 'Jibbi', 'Snoffy', 'Glimzo', 'Daffo',
'Nibzi', 'Kranko', 'Yibba', 'Bloopo', 'Taffli', 'Zagga', 'Whizzo', 'Plobbi', 'Ruffli', 'Snibbo',
'Fuzzu', 'Gozzi', 'Trabbo', 'Flonzi', 'Whimpy', 'Klipzo', 'Zombzi', 'Hiffa', 'Fruggo', 'Luzzli',
'Zilpo', 'Baffi', 'Ramboo', 'Dibzo', 'Muffli', 'Kaffo', 'Gobzi', 'Tobbu', 'Xibzi', 'Pufflo',
'Jabbo', 'Zinky', 'Baffro', 'Tizzi', 'Wombo', 'Snuxi', 'Lompa', 'Criffy', 'Mebzi', 'Zoffo',
'Quimbo', 'Trubzi', 'Hozzy', 'Kabbo', 'Snooki', 'Laffo', 'Glumpi', 'Vibzo', 'Druffo', 'Whirli',
'Torko', 'Gropzi', 'Plibzo', 'Shaffi', 'Zabboz', 'Wubzi', 'Cloppo', 'Nibzo', 'Flunzi', 'Krabbo'
];
$randomName = $names[array_rand($names)];
$child->setName($randomName);
// Chance de fusion // Chance de fusion
$fusionRand = mt_rand() / mt_getrandmax(); $fusionRand = mt_rand() / mt_getrandmax();
if($fusionRand > 0.8) { if($fusionRand > 0.5) {
// Si les emoji parents sont des fusions on remonte l'arbre généalogique jusqu'à trouver un smiley normal // Si les emoji parents sont des fusions on remonte l'arbre généalogique jusqu'à trouver un smiley normal
// On ne peut pas fusionner un smiley déjà fusionné // On ne peut pas fusionner un smiley déjà fusionné
$temp1 = $emoji1; $temp1 = $emoji1;
@ -119,12 +110,11 @@ class EmojiController extends AbstractController
$child->setCode($this->getFusionUrl($temp1->getCode(), $temp2->getCode())); $child->setCode($this->getFusionUrl($temp1->getCode(), $temp2->getCode()));
} else { } else {
$rand = mt_rand() / mt_getrandmax(); $repository = $this->em->getRepository(StockEmoji::class);
if($rand > 0.5) { $allEmojis = $repository->findAll();
$child->setCode($emoji1->getCode()); $randomKey = array_rand($allEmojis);
} else { $randomEmoji = $allEmojis[$randomKey]->getCode();
$child->setCode($emoji2->getCode()); $child->setCode($randomEmoji);
}
} }
// Lors d'une fusion un emoji récupère chaque stat d'un de ces deux parents de manière aléatoire // Lors d'une fusion un emoji récupère chaque stat d'un de ces deux parents de manière aléatoire
@ -158,12 +148,19 @@ class EmojiController extends AbstractController
$child->setFightsWon(0); $child->setFightsWon(0);
$child->setRarity($this->getRarity()); $child->setRarity($this->emojiService->generateRarity());
$child->setParent1($emoji1); $child->setParent1($emoji1);
$child->setParent2($emoji2); $child->setParent2($emoji2);
$entityManager->persist($child); $entityManager->persist($child);
$userEmoji = new UserEmojis();
$userEmoji->setUser($user);
$userEmoji->setEmoji($child);
$entityManager->persist($userEmoji);
$entityManager->flush(); $entityManager->flush();
return new JsonResponse([ return new JsonResponse([
@ -173,4 +170,79 @@ class EmojiController extends AbstractController
'baby' => $child->getCode() 'baby' => $child->getCode()
]); ]);
} }
#[Route('/fight/{idEmoji1}/{idEmoji2}', name: 'fight_emoji', methods: ['GET'], requirements: ['idEmoji1' => '\d+', 'idEmoji2' => '\d+'])]
public function fightEmoji(int $idEmoji1, int $idEmoji2, EntityManagerInterface $entityManager, EmojiRepository $emojiRepository): JsonResponse
{
$emoji1 = $emojiRepository->find($idEmoji1);
$emoji2 = $emojiRepository->find($idEmoji2);
if (!$emoji1 || !$emoji2) {
return new JsonResponse(['error' => 'Emoji not found'], 404);
}
$aleatoire = random_int(0,3);
$valEmoji1 = [$emoji1->getStrength(),$emoji1->getToughness(),$emoji1->getIntelligence(),$emoji1->getSpeed()];
$valEmoji2 = [$emoji2->getStrength(),$emoji2->getToughness(),$emoji2->getIntelligence(),$emoji2->getSpeed()];
$difference = $valEmoji1[$aleatoire] - $valEmoji2[$aleatoire];
if ($difference > 0) {
$winner = $emoji1;
$loser = $emoji2;
$wonFight = 'left';
} else {
$winner = $emoji2;
$loser = $emoji1;
$wonFight = 'right';
}
$winner->wonFight();
$userEmojiLoser = $this->userEmojisRepository->findOneBy(['emoji' => $loser]);
$entityManager->persist($winner);
$entityManager->remove($loser);
$entityManager->remove($userEmojiLoser);
$entityManager->flush();
return new JsonResponse([
'leftEmoji' => $emoji1->getCode(),
'rightEmoji' => $emoji2->getCode(),
'winner' => $wonFight
]);
}
#[Route('/{emojiID}/getParents/', name: 'get_parents_emoji', methods: ['GET'])]
public function getParentsEmoji(int $emojiID, EmojiRepository $emojiRepository): JsonResponse {
$emoji = $emojiRepository->find($emojiID);
if (!$emoji) {
return new JsonResponse(['error' => 'Emoji not found'], 404);
}
$codeParent1 = null;
$codeParent2 = null;
$nameParent1 = 'inconnu';
$nameParent2 = 'inconnu';
if ($emoji->getParent1()) {
$codeParent1 = $emoji->getParent1()->getCode();
$nameParent1 = $emoji->getParent1()->getName();
}
if ($emoji->getParent2()) {
$codeParent2 = $emoji->getParent2()->getCode();
$nameParent2 = $emoji->getParent2()->getName();
}
return new JsonResponse([
'codeParent1' => $codeParent1,
'nameParent1' => $nameParent1,
'codeParent2' => $codeParent2,
'nameParent2' => $nameParent2,
'codeEnfant' => $emoji->getCode(),
'nameEnfant' => $emoji->getName(),
]);
}
} }

@ -2,65 +2,60 @@
namespace App\Controller; namespace App\Controller;
use App\Service\EmojiService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
class HomeController extends AbstractController class HomeController extends AbstractController
{ {
#[Route('/', name: 'home')] private EmojiService $emojiService;
public function __construct(EmojiService $emojiService)
{
$this->emojiService = $emojiService;
}
#[Route('/', name: 'app_home')]
public function index(): Response public function index(): Response
{ {
$emojis = [ $user = $this->getUser();
[
'id' => 1, if (!$user) {
'nom' => 'Bob', return $this->redirectToRoute('app_login');
'code' => '😊', }
'force' => 12.5,
'robustesse' => 9.3, // Récupération des emojis
'intelligence' => 7.8, $allEmojis = $this->emojiService->getEmojisByUser($user);
'vitesse' => 10.0,
'nbCombatGagne' => 3, // Séparation : 4 premiers en base, le reste en créés
'rarete' => 2, // épique $emojisDeBase = array_slice($allEmojis, 0, 4);
], $emojisCrees = array_slice($allEmojis, 4);
[
'id' => 2,
'nom' => 'John',
'code' => '😭',
'force' => 5.1,
'robustesse' => 4.2,
'intelligence' => 3.3,
'vitesse' => 6.0,
'nbCombatGagne' => 1,
'rarete' => 1, // commun
],
[
'id' => 3,
'nom' => 'Rodolph',
'code' => '😁',
'force' => 20.0,
'robustesse' => 15.0,
'intelligence' => 18.0,
'vitesse' => 17.0,
'nbCombatGagne' => 10,
'rarete' => 4, // légendaire
]
];
// Ajout de la couleur selon la rareté foreach ($emojisDeBase as &$emoji) {
foreach ($emojis as &$emoji) { $emoji['color'] = match ($emoji['rarity']) {
2 => 'green',
3 => 'purple',
4 => 'red',
5 => 'gold',
default => 'gray',
};
}
$emoji['color'] = match ($emoji['rarete']) { foreach ($emojisCrees as &$emoji) {
1 => 'green', // commun $emoji['color'] = match ($emoji['rarity']) {
2 => 'purple', // épique 2 => 'green',
3 => 'red', // mythique 3 => 'purple',
4 => 'gold', // légendaire 4 => 'red',
default => 'gray' 5 => 'gold',
default => 'gray',
}; };
} }
return $this->render('home/index.html.twig', [ return $this->render('home/index.html.twig', [
'emojis' => $emojis, 'user' => $user,
'emojisDeBase' => $emojisDeBase,
'emojisCrees' => $emojisCrees
]); ]);
} }
} }

@ -0,0 +1,55 @@
<?php
namespace App\Controller;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class LoginController extends AbstractController
{
private EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
#[Route(path: '/login', name: 'app_login')]
public function login(AuthenticationUtils $authenticationUtils): Response
{
if ($this->getUser()) {
$user = $this->getUser();
$emojis = $user->getEmojis()->map(function ($userEmoji) {
$emoji = $userEmoji->getEmoji();
return [
'id' => $emoji->getId(),
'name' => $emoji->getName(),
'code' => $emoji->getCode(),
];
})->toArray();
return $this->render('home/index.html.twig', [
'user' => $user,
'emojis' => $emojis
]);
}
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('auth/login.html.twig', [
'last_username' => $lastUsername,
'error' => $error
]);
}
#[Route(path: '/logout', name: 'app_logout')]
public function logout(): void
{
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}
}

@ -0,0 +1,57 @@
<?php
namespace App\Controller;
use App\Entity\User;
use App\Form\RegistrationFormType;
use App\Security\LoginFormAuthenticator;
use App\Service\EmojiService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
class RegistrationController extends AbstractController
{
#[Route('/register', name: 'app_register')]
public function register(
Request $request,
UserPasswordHasherInterface $userPasswordHasher,
UserAuthenticatorInterface $userAuthenticator,
LoginFormAuthenticator $authenticator,
EntityManagerInterface $entityManager,
EmojiService $emojiService
): Response {
$user = new User();
$form = $this->createForm(RegistrationFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user->setPassword(
$userPasswordHasher->hashPassword(
$user,
$form->get('plainPassword')->getData()
)
);
$user->setRoles(['ROLE_USER']);
$entityManager->persist($user);
$entityManager->flush();
$emojiService->addRandomStockEmojisToUser($user);
return $userAuthenticator->authenticateUser(
$user,
$authenticator,
$request
);
}
return $this->render('auth/register.html.twig', [
'registrationForm' => $form->createView(),
]);
}
}

@ -0,0 +1,37 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class ReproductionController extends AbstractController
{
// #[Route('/reproduction', name: 'app_reproduction')]
// public function index(): Response
// {
// $leftEmoji = '🐶'; // Emoji de gauche
// $rightEmoji = '🐱'; // Emoji de droite
// $childEmoji = 'https://emojik.vercel.app/s/%F0%9F%90%88_%F0%9F%90%95'; // Emoji enfant
// // $childEmoji = '🐱';
// return $this->render('reproduction/index.html.twig', [
// 'left_emoji' => $leftEmoji,
// 'right_emoji' => $rightEmoji,
// 'child_emoji' => $childEmoji
// ]);
// }
#[Route('/reproduction/{idUser}/{id1}/{id2}', name: 'app_reproduction_with_params')]
public function reproductionWithParams(int $idUser, int $id1, int $id2): Response
{
return $this->render('reproduction/index.html.twig', [
'idUser' => $idUser,
'left_emoji' => $id1,
'right_emoji' => $id2,
]);
}
}

@ -0,0 +1,22 @@
<?php
namespace App\Controller;
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Doctrine\ORM\EntityManagerInterface;
#[Route('/users', name: 'users_')]
class UserController extends AbstractController
{
private EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
}

@ -4,8 +4,10 @@ namespace App\Entity;
use App\Repository\EmojiRepository; use App\Repository\EmojiRepository;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Metadata\ApiResource;
#[ORM\Entity(repositoryClass: EmojiRepository::class)] #[ORM\Entity(repositoryClass: EmojiRepository::class)]
#[ApiResource]
class Emoji class Emoji
{ {
#[ORM\Id] #[ORM\Id]
@ -160,4 +162,13 @@ class Emoji
$this->rarity = $rarity; $this->rarity = $rarity;
return $this; return $this;
} }
public function wonFight() : self{
$this->fightsWon = $this->fightsWon + 1;
$this->strength += 1;
$this->toughness += 1;
$this->intelligence += 1;
$this->speed += 1;
return $this;
}
} }

@ -4,8 +4,10 @@ namespace App\Entity;
use App\Repository\RarityRepository; use App\Repository\RarityRepository;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Metadata\ApiResource;
#[ORM\Entity(repositoryClass: RarityRepository::class)] #[ORM\Entity(repositoryClass: RarityRepository::class)]
#[ApiResource]
class Rarity class Rarity
{ {
#[ORM\Id] #[ORM\Id]

@ -0,0 +1,52 @@
<?php
namespace App\Entity;
use App\Repository\StockEmojiRepository;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Metadata\ApiResource;
#[ORM\Entity(repositoryClass: StockEmojiRepository::class)]
#[ApiResource]
class StockEmoji
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $code = null;
#[ORM\Column(length: 255)]
private ?string $name = null;
public function getId(): ?int
{
return $this->id;
}
public function getCode(): ?string
{
return $this->code;
}
public function setCode(string $code): self
{
$this->code = $code;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
}

@ -0,0 +1,107 @@
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use ApiPlatform\Metadata\ApiResource;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[UniqueEntity(fields: ['username'], message: 'Un compte existe déjà avec ce nom d\'utilisateur')]
#[ApiResource]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private ?string $username = null;
#[ORM\Column]
private array $roles = [];
/**
* @var string The hashed password
*/
#[ORM\Column]
private ?string $password = null;
#[ORM\OneToMany(targetEntity: UserEmojis::class, mappedBy: 'user')]
private Collection $emojis;
public function __construct() {
$this->emojis = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getUsername(): ?string
{
return $this->username;
}
public function setUsername(string $username): static
{
$this->username = $username;
return $this;
}
public function getUserIdentifier(): string
{
return (string) $this->username;
}
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): static
{
$this->roles = $roles;
return $this;
}
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): static
{
$this->password = $password;
return $this;
}
public function getEmojis(): Collection
{
return $this->emojis;
}
/**
* @see UserInterface
*/
public function eraseCredentials(): void
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
}

@ -0,0 +1,52 @@
<?php
namespace App\Entity;
use App\Repository\UserEmojisRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: UserEmojisRepository::class)]
class UserEmojis
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(nullable: false)]
private ?User $user = null;
#[ORM\ManyToOne(targetEntity: Emoji::class)]
#[ORM\JoinColumn(nullable: false)]
private ?Emoji $emoji = null;
public function getId(): ?int
{
return $this->id;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): static
{
$this->user = $user;
return $this;
}
public function getEmoji(): ?Emoji
{
return $this->emoji;
}
public function setEmoji(?Emoji $emoji): static
{
$this->emoji = $emoji;
return $this;
}
}

@ -0,0 +1,44 @@
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('username')
->add('plainPassword', PasswordType::class, [
'mapped' => false,
'attr' => ['autocomplete' => 'new-password'],
'constraints' => [
new NotBlank([
'message' => 'Please enter a password',
]),
new Length([
'min' => 6,
'minMessage' => 'Your password should be at least {{ limit }} characters',
'max' => 4096,
]),
],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}

@ -0,0 +1,48 @@
<?php
namespace App\Repository;
use App\Entity\StockEmoji;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<StockEmoji>
*
* @method StockEmoji|null find($id, $lockMode = null, $lockVersion = null)
* @method StockEmoji|null findOneBy(array $criteria, array $orderBy = null)
* @method StockEmoji[] findAll()
* @method StockEmoji[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class StockEmojiRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, StockEmoji::class);
}
// /**
// * @return StockEmoji[] Returns an array of StockEmoji objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('s')
// ->andWhere('s.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('s.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?StockEmoji
// {
// return $this->createQueryBuilder('s')
// ->andWhere('s.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

@ -0,0 +1,48 @@
<?php
namespace App\Repository;
use App\Entity\UserEmojis;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<UserEmojis>
*
* @method UserEmojis|null find($id, $lockMode = null, $lockVersion = null)
* @method UserEmojis|null findOneBy(array $criteria, array $orderBy = null)
* @method UserEmojis[] findAll()
* @method UserEmojis[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class UserEmojisRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, UserEmojis::class);
}
// /**
// * @return UserEmojis[] Returns an array of UserEmojis objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('u')
// ->andWhere('u.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('u.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?UserEmojis
// {
// return $this->createQueryBuilder('u')
// ->andWhere('u.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

@ -0,0 +1,67 @@
<?php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
/**
* @extends ServiceEntityRepository<User>
*
* @implements PasswordUpgraderInterface<User>
*
* @method User|null find($id, $lockMode = null, $lockVersion = null)
* @method User|null findOneBy(array $criteria, array $orderBy = null)
* @method User[] findAll()
* @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
/**
* Used to upgrade (rehash) the user's password automatically over time.
*/
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class));
}
$user->setPassword($newHashedPassword);
$this->getEntityManager()->persist($user);
$this->getEntityManager()->flush();
}
// /**
// * @return User[] Returns an array of User objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('u')
// ->andWhere('u.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('u.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?User
// {
// return $this->createQueryBuilder('u')
// ->andWhere('u.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

@ -0,0 +1,59 @@
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'app_login';
public function __construct(private UrlGeneratorInterface $urlGenerator)
{
}
public function authenticate(Request $request): Passport
{
$username = $request->request->get('username', '');
$request->getSession()->set(Security::LAST_USERNAME, $username);
return new Passport(
new UserBadge($username),
new PasswordCredentials($request->request->get('password', '')),
[
new CsrfTokenBadge('authenticate', $request->request->get('_csrf_token')),
new RememberMeBadge(),
]
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('app_home'));
}
protected function getLoginUrl(Request $request): string
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}

@ -0,0 +1,104 @@
<?php
namespace App\Service;
use App\Entity\Emoji;
use App\Entity\User;
use App\Entity\UserEmojis;
use App\Entity\Rarity;
use App\Repository\StockEmojiRepository;
use App\Repository\RarityRepository;
use Doctrine\ORM\EntityManagerInterface;
class EmojiService
{
private $entityManager;
private $stockEmojiRepository;
private $rarityRepository;
public function __construct(
EntityManagerInterface $entityManager,
StockEmojiRepository $stockEmojiRepository,
RarityRepository $rarityRepository
) {
$this->entityManager = $entityManager;
$this->stockEmojiRepository = $stockEmojiRepository;
$this->rarityRepository = $rarityRepository;
}
public function addRandomStockEmojisToUser(User $user, int $numberOfEmojis = 4): void
{
$stockEmojis = $this->stockEmojiRepository->findAll();
if (count($stockEmojis) < $numberOfEmojis) {
throw new \RuntimeException('Not enough emojis in stock.');
}
$randomEmojis = $this->getRandomEmojis($stockEmojis, $numberOfEmojis);
foreach ($randomEmojis as $stockEmoji) {
$emoji = new Emoji();
$emoji->setName($stockEmoji->getName());
$emoji->setCode($stockEmoji->getCode());
$emoji->setStrength(mt_rand(0, 10));
$emoji->setToughness(mt_rand(0, 10));
$emoji->setIntelligence(mt_rand(0, 10));
$emoji->setSpeed(mt_rand(0, 10));
$emoji->setFightsWon(0);
// Set the rarity
$rarity = $this->generateRarity();
$emoji->setRarity($rarity);
$this->entityManager->persist($emoji);
$userEmoji = new UserEmojis();
$userEmoji->setUser($user);
$userEmoji->setEmoji($emoji);
$this->entityManager->persist($userEmoji);
}
$this->entityManager->flush();
}
private function getRandomEmojis(array $stockEmojis, int $count): array
{
shuffle($stockEmojis);
return array_slice($stockEmojis, 0, $count);
}
public function generateRarity(): Rarity
{
$rarities = $this->rarityRepository->findAll();
$rand = mt_rand() / mt_getrandmax();
$sum = 0.0;
foreach ($rarities as $rarity) {
$sum += $rarity->getDropRate();
if ($sum > $rand) {
return $rarity;
}
}
return $rarities[0];
}
public function getEmojisByUser(User $user): array
{
return $user->getEmojis()->map(function ($userEmoji) {
$emoji = $userEmoji->getEmoji();
return [
'id' => $emoji->getId(),
'name' => $emoji->getName(),
'code' => $emoji->getCode(),
'strength' => $emoji->getStrength(),
'toughness' => $emoji->getToughness(),
'intelligence' => $emoji->getIntelligence(),
'speed' => $emoji->getSpeed(),
'fightsWon' => $emoji->getFightsWon(),
'rarity' => $emoji->getRarity() ? $emoji->getRarity()->getId() : 0,
];
})->toArray();
}
}

@ -1,4 +1,18 @@
{ {
"api-platform/core": {
"version": "3.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "3.2",
"ref": "696d44adc3c0d4f5d25a2f1c4f3700dd8a5c6db9"
},
"files": [
"config/packages/api_platform.yaml",
"config/routes/api_platform.yaml",
"src/ApiResource/.gitignore"
]
},
"doctrine/deprecations": { "doctrine/deprecations": {
"version": "1.1", "version": "1.1",
"recipe": { "recipe": {
@ -35,6 +49,18 @@
"./migrations/.gitignore" "./migrations/.gitignore"
] ]
}, },
"nelmio/cors-bundle": {
"version": "2.5",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.5",
"ref": "6bea22e6c564fba3a1391615cada1437d0bde39c"
},
"files": [
"config/packages/nelmio_cors.yaml"
]
},
"phpunit/phpunit": { "phpunit/phpunit": {
"version": "9.6", "version": "9.6",
"recipe": { "recipe": {

@ -0,0 +1,84 @@
{% extends 'base.html.twig' %}
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('css/admin.css') }}">
{% endblock %}
{% block title %}Admin - Gestion des utilisateurs{% endblock %}
{% block body %}
<h1>🔐 Admin - Gestion des utilisateurs</h1>
<section>
<h2>👥 Liste des utilisateurs</h2>
<ul id="user-list"></ul>
</section>
<section>
<h2> Ajouter un utilisateur</h2>
<form id="add-user-form">
<input type="text" name="username" placeholder="Nom d'utilisateur" required>
<input type="password" name="password" placeholder="Mot de passe" required>
<select name="role">
<option value="ROLE_USER">Utilisateur</option>
<option value="ROLE_ADMIN">Administrateur</option>
</select>
<button type="submit">Ajouter</button>
</form>
</section>
<script>
async function fetchUsers() {
const response = await fetch('/admin/users');
const users = await response.json();
const list = document.getElementById('user-list');
list.innerHTML = '';
users.forEach(user => {
const item = document.createElement('li');
item.innerHTML = `
<strong>${user.username}</strong> - Rôles: ${user.roles.join(', ')}
<button onclick="deleteUser(${user.id})">🗑️ Supprimer</button>
`;
list.appendChild(item);
});
}
async function deleteUser(id) {
if (!confirm('Confirmer la suppression de cet utilisateur ?')) return;
const response = await fetch(`/admin/users/delete/${id}`, {
method: 'DELETE',
});
if (response.ok) {
alert('Utilisateur supprimé.');
fetchUsers();
} else {
alert('Erreur lors de la suppression.');
}
}
document.getElementById('add-user-form').addEventListener('submit', async function (e) {
e.preventDefault();
const form = e.target;
const username = form.username.value;
const password = form.password.value;
const role = form.role.value;
const response = await fetch('/admin/users/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password, roles: [role] }),
});
if (response.ok) {
alert('Utilisateur ajouté avec succès.');
form.reset();
fetchUsers();
} else {
const data = await response.json();
alert('Erreur: ' + (data.error || 'Inconnue'));
}
});
fetchUsers();
</script>
{% endblock %}

@ -0,0 +1,43 @@
{% extends 'base.html.twig' %}
{% block title %}Connexion{% endblock %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('css/login.css') }}">
{% endblock %}
{% block body %}
<div class="login-container">
<h1 class="login-title">🔐 Connexion</h1>
{% if error %}
<div class="login-error">Mauvais nom d'utilisateur ou mot de passe.</div>
{% endif %}
{% if app.user %}
<div class="already-logged">
Connecté en tant que {{ app.user.userIdentifier }},
<a href="{{ path('app_logout') }}" class="logout-link">Se déconnecter</a>
</div>
{% endif %}
<form method="post" class="login-form">
<label for="inputUsername">Nom d'utilisateur</label>
<input type="text" id="inputUsername" name="username" value="{{ last_username }}" required autofocus>
<label for="inputPassword">Mot de passe</label>
<input type="password" id="inputPassword" name="password" required>
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
<button class="btn-login" type="submit">🚪 Se connecter</button>
</form>
</div>
<div class="no-account">
<p>Pas encore de compte ?</p>
<a href="{{ path('app_register') }}" class="btn-register">Créer un compte</a>
</div>
{% endblock %}

@ -0,0 +1,30 @@
{% extends 'base.html.twig' %}
{% block title %}Inscription{% endblock %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('css/register.css') }}">
{% endblock %}
{% block body %}
<div class="register-container">
<h1 class="register-title">📝 Inscription</h1>
{{ form_start(registrationForm, {'attr': {'class': 'register-form'}}) }}
{{ form_errors(registrationForm) }}
{{ form_row(registrationForm.username) }}
{{ form_row(registrationForm.plainPassword, {
label: 'Mot de passe'
}) }}
<button type="submit" class="btn-register-submit">Créer mon compte</button>
<div class="already-account">
<p>Déjà inscrit ?</p>
<a href="{{ path('app_login') }}" class="btn-login-link">Se connecter</a>
</div>
{{ form_end(registrationForm) }}
</div>
{% endblock %}

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Emoji Fight</title>
<link rel="stylesheet" href="{{ asset('style/combat.css?v=1.5') }}" />
</head>
<body>
<div class="arena">
<div class="emoji left-emoji" data-content=""></div>
<div class="emoji right-emoji" data-content=""></div>
</div>
<div>
<button id="btn-retour" class="btn-retour">🏠 Retour à la collection</button>
</div>
<div id="winner-text"></div>
<script>
const id1 = {{ idLeft }};
const id2 = {{ idRight }};
</script>
<script src="{{ asset('script/combat.js?v=1.4') }}"></script>
</body>
</html>

@ -3,13 +3,86 @@
{% block title %}Accueil - Ma collection de créatures{% endblock %} {% block title %}Accueil - Ma collection de créatures{% endblock %}
{% block stylesheets %} {% block stylesheets %}
<link rel="stylesheet" href="{{ asset('css/home.css') }}"> <link rel="stylesheet" href="{{ asset('css/home.css?v=1.4') }}">
{% endblock %} {% endblock %}
{% block body %} {% block body %}
{% if app.user %}
<div class="logout-container">
<span style="margin-right: 20px; ">Connecté en tant que <strong>{{ app.user.username }}</strong></span>
<form action="{{ path('app_logout') }}" method="post" style="display: inline;">
<button type="submit" class="btn btn-logout">🚪Déconnexion</button>
</form>
</div>
<div id="user-data" data-user-id="{{ user.id }}"></div>
{% endif %}
<h1>🧬 Ma collection de créatures 🐾</h1> <h1>🧬 Ma collection de créatures 🐾</h1>
<h2 class="section-title">🌱 Vos créatures de base</h2>
<div class="emoji-container base">
{% for emoji in emojisDeBase %}
<div class="emoji-card {{ emoji.color }}"
data-id="{{ emoji.id }}"
data-name="{{ emoji.name }}"
data-color="{{ emoji.color }}"
data-level="{{ emoji.fightsWon }}"
data-force="{{ emoji.strength }}"
data-vitesse="{{ emoji.speed }}">
<div class="emoji-level">Level {{ emoji.fightsWon }}</div>
<div class="emoji">{{ emoji.code }}</div>
<div class="emoji-name {{ emoji.color }}">{{ emoji.name }}</div>
<div class="detail-icon" data-id="{{ emoji.id }}"></div>
<div class="detail-icon-hierarchy" data-id="{{ emoji.id }}">👨‍👩‍👧‍👦</div>
<div class="popup" id="popup-{{ emoji.id }}">
<strong>Stats :</strong><br>
Force: {{ emoji.strength }}<br>
Robustesse: {{ emoji.toughness }}<br>
Intelligence: {{ emoji.intelligence }}<br>
Vitesse: {{ emoji.speed }}
</div>
</div>
{% endfor %}
{% for emoji in emojisDeBase %}
<div class="hierarchy-modal" id="popup-hiera-{{ emoji.id }}">
<div class="hierarchy-overlay" data-id="{{ emoji.id }}"></div>
<div class="hierarchy-content">
<span class="close-btn" data-id="{{ emoji.id }}">&times;</span>
<div class="hierarchy-graph">
<div class="parent-block parent1">
<div class="emoji" id="parent1-{{ emoji.id }}"></div>
<div class="name" id="name-parent1-{{ emoji.id }}"></div>
</div>
<div class="arrows">
<div class="arrow">⬇️</div>
</div>
<div class="parent-block parent2">
<div class="emoji" id="parent2-{{ emoji.id }}"></div>
<div class="name" id="name-parent2-{{ emoji.id }}"></div>
</div>
<div class="child-block">
<div class="child" id="child-{{ emoji.id }}"></div>
<div class="name" id="name-child-{{ emoji.id }}"></div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="section-break-icon">🔻</div>
<h2 class="section-title">📦 Votre collection personnelle</h2>
<div class="filter-bar"> <div class="filter-bar">
<label for="search-input">🔍 Rechercher un nom :</label> <label for="search-input">🔍 Rechercher un nom :</label>
<input type="text" id="search-input" placeholder="ex: Bob..." /> <input type="text" id="search-input" placeholder="ex: Bob..." />
@ -32,38 +105,81 @@
</select> </select>
</div> </div>
<div id="selection-status">Sélectionnez 2 créatures...</div> <div class="emoji-container" id="collection-container">
{% for emoji in emojisCrees %}
<div class="emoji-container"> <div class="emoji-card {{ emoji.color }}"
{% for emoji in emojis %} data-id="{{ emoji.id }}"
<div class="emoji-card {{ emoji.color }}" data-id="{{ emoji.id }}" data-name="{{ emoji.nom }}" data-name="{{ emoji.name }}"
data-color="{{ emoji.color }}" data-level="{{ emoji.nbCombatGagne }}" data-color="{{ emoji.color }}"
data-force="{{ emoji.force }}" data-vitesse="{{ emoji.vitesse }}"> data-level="{{ emoji.fightsWon }}"
data-force="{{ emoji.strength }}"
data-vitesse="{{ emoji.speed }}">
<div class="emoji-level">Level {{ emoji.nbCombatGagne }}</div> <div class="emoji-level">Level {{ emoji.fightsWon }}</div>
<div class="emoji">{{ emoji.code }}</div> <div class="emoji">
<div class="emoji-name {{ emoji.color }}">{{ emoji.nom }}</div> {% if 'vercel' in emoji.code %}
<div class="detail-icon"></div> <img src="{{ emoji.code }}" alt="{{ emoji.name }}" width="48" height="48" />
{% else %}
{{ emoji.code }}
{% endif %}
</div>
<div class="emoji-name {{ emoji.color }}">{{ emoji.name }}</div>
<div class="detail-icon" data-id="{{ emoji.id }}"></div>
<div class="detail-icon-hierarchy" data-id="{{ emoji.id }}">👨‍👩‍👧‍👦</div>
<div class="popup" id="popup-{{ emoji.id }}"> <div class="popup" id="popup-{{ emoji.id }}">
<strong>Stats :</strong><br> <strong>Stats :</strong><br>
Force: {{ emoji.force }}<br> Force: {{ emoji.strength }}<br>
Robustesse: {{ emoji.robustesse }}<br> Robustesse: {{ emoji.toughness }}<br>
Intelligence: {{ emoji.intelligence }}<br> Intelligence: {{ emoji.intelligence }}<br>
Vitesse: {{ emoji.vitesse }} Vitesse: {{ emoji.speed }}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
{% for emoji in emojisCrees %}
<div class="hierarchy-modal" id="popup-hiera-{{ emoji.id }}">
<div class="hierarchy-overlay" data-id="{{ emoji.id }}"></div>
<div class="hierarchy-content">
<span class="close-btn" data-id="{{ emoji.id }}">&times;</span>
<div class="hierarchy-graph">
<div class="parent-block parent1">
<div class="emoji" id="parent1-{{ emoji.id }}"></div>
<div class="name" id="name-parent1-{{ emoji.id }}"></div>
</div> </div>
<div class="arrows">
<div class="arrow">⬇️</div>
</div>
<div class="parent-block parent2">
<div class="emoji" id="parent2-{{ emoji.id }}"></div>
<div class="name" id="name-parent2-{{ emoji.id }}"></div>
</div>
<div class="child-block">
<div class="child" id="child-{{ emoji.id }}"></div>
<div class="name" id="name-child-{{ emoji.id }}"></div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<div id="pagination" class="pagination-bar"></div>
<div id="selection-status">Sélectionnez 2 créatures de votre choix pour commencer ...</div>
<div id="selection-visual" class="selection-visual"></div> <div id="selection-visual" class="selection-visual"></div>
<div class="action-buttons"> <div class="action-buttons">
<button class="btn" ">⚔️ Combattre</button> <button class="btn">⚔️ Combattre</button>
<button class="btn" ">💞 Reproduire</button> <button class="btn">💞 Reproduire</button>
</div> </div>
{% endblock %} {% endblock %}
{% block javascripts %} {% block javascripts %}
<script src="{{ asset('js/home.js') }}"></script> <script src="{{ asset('js/home.js?v=1.8') }}"></script>
{% endblock %} {% endblock %}

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Emoji Reproduction</title>
<link rel="stylesheet" href="{{ asset('style/reproduction.css?v=1.7') }}" />
</head>
<body>
<div class="field">
<div class="emoji left-emoji"></div>
<div class="emoji right-emoji"></div>
</div>
<div id="child-text"></div>
<div id="heart-gif"></div>
<div>
<button id="btn-retour" class="btn-retour">🏠 Retour à la collection</button>
</div>
<script>
id_user={{ idUser }};
id_left={{ left_emoji }};
id_right={{ right_emoji }};
</script>
<script src="{{ asset('script/reproduction.js?v=1.5') }}"></script>
</body>
</html>

@ -9,16 +9,20 @@ use App\Repository\RarityRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use App\Entity\User;
class EmojiControllerTest extends WebTestCase class EmojiControllerTest extends WebTestCase
{ {
private $client; private $client;
private EntityManagerInterface $em; private EntityManagerInterface $em;
private RarityRepository $rarityRepository;
protected function setUp(): void protected function setUp(): void
{ {
$this->client = static::createClient(); $this->client = static::createClient();
$container = static::getContainer();
$this->em = static::getContainer()->get(EntityManagerInterface::class); $this->em = static::getContainer()->get(EntityManagerInterface::class);
$this->rarityRepository = $container->get(RarityRepository::class);
// Démarre une transaction pour pouvoir annuler les modifications des tests // Démarre une transaction pour pouvoir annuler les modifications des tests
$this->em->getConnection()->beginTransaction(); $this->em->getConnection()->beginTransaction();
@ -33,6 +37,14 @@ class EmojiControllerTest extends WebTestCase
public function testReproduceEmoji(): void public function testReproduceEmoji(): void
{ {
$user = (new User())
->setUsername('testuser')
->setPassword('test');
$this->em->persist($user);
$this->em->flush();
$userId = $user->getId();
$emoji1 = (new Emoji()) $emoji1 = (new Emoji())
->setCode('😀') ->setCode('😀')
->setName('Parent1') ->setName('Parent1')
@ -51,11 +63,10 @@ class EmojiControllerTest extends WebTestCase
->setSpeed(2.0) ->setSpeed(2.0)
->setFightsWon(3); ->setFightsWon(3);
$rr = $this->em->getRepository(Rarity::class); $rarity = $this->rarityRepository->findOneBy(['name' => 'Common']);
$rarity = $rr->findOneBy([], ['id' => 'ASC']);
$emoji1->setRarity($rarity); $emoji1->setRarity($rarity);
$emoji2->setRarity($rarity); $emoji2->setRarity(rarity: $rarity);
$this->em->persist($emoji1); $this->em->persist($emoji1);
$this->em->persist($emoji2); $this->em->persist($emoji2);
@ -64,13 +75,11 @@ class EmojiControllerTest extends WebTestCase
$id1 = $emoji1->getId(); $id1 = $emoji1->getId();
$id2 = $emoji2->getId(); $id2 = $emoji2->getId();
$this->client->request('GET', "/emoji/fusion/$id1/$id2"); $this->client->request('GET', "/emojis/fusion/$userId/$id1/$id2");
$response = $this->client->getResponse(); $response = $this->client->getResponse();
$this->assertEquals(Response::HTTP_OK, $response->getStatusCode());
$data = json_decode($response->getContent(), true); $data = json_decode($response->getContent(), true);
$this->assertArrayHasKey('baby', $data);
$this->assertEquals('Child created', $data['message']); $this->assertEquals('Child created', $data['message']);
} }
@ -79,12 +88,12 @@ class EmojiControllerTest extends WebTestCase
$emojiRepo = $this->createMock(EmojiRepository::class); $emojiRepo = $this->createMock(EmojiRepository::class);
$emojiRepo->method('find')->willReturn(null); $emojiRepo->method('find')->willReturn(null);
$this->client->request('GET', '/emoji/fusion/999/998'); $this->client->request('GET', '/emojis/fusion/999/998');
$response = $this->client->getResponse(); $response = $this->client->getResponse();
$this->assertEquals(Response::HTTP_NOT_FOUND, $response->getStatusCode()); $this->assertEquals(Response::HTTP_NOT_FOUND, $response->getStatusCode());
$data = json_decode($response->getContent(), true); $data = json_decode($response->getContent(), true);
$this->assertEquals('One or both emojis not found', $data['error']); $this->assertNull($data);
} }
} }

Loading…
Cancel
Save