KO

Slave Rift

tueb2024 제작 · 슈팅
5.0 (1)
▶ 플레이 💬 후기 소스 🕘 버전 편집 GitHub
games/slave-rift / src / Gfx / MinitelRosePoster.php · commit 9ea6094
<?phpdeclare(strict_types=1);namespace App\Gfx;/** * MinitelRosePoster — générateur procédural d'affiches Minitel rose. * Adaptation directe du générateur DeepSeek fourni pour Slave Rift. */final class MinitelRosePoster{    private const NAMES = [        'Ulla', 'Aline', 'Jane', 'Sarah', 'Mytilène',        'Aurélie', 'Émilie', 'Céline', 'Élodie', 'Julie', 'Marie',        'Stéphanie', 'Audrey', 'Laetitia', 'Virginie', 'Sophie',        'Mélanie', 'Sabrina', 'Vanessa', 'Caroline', 'Delphine',        'Amandine', 'Jennifer', 'Jessica', 'Nathalie', 'Valérie',        'Christelle', 'Sandra', 'Karine', 'Séverine', 'Magali',        'Coralie', 'Marion', 'Lucie', 'Camille', 'Chloé', 'Manon',        'Léa', 'Anaïs', 'Pauline', 'Justine', 'Océane', 'Charlotte',        'Mathilde', 'Clémentine', 'Margaux', 'Inès', 'Lou', 'Jade',        'Lola', 'Zoé', 'Nina', 'Éva', 'Alice', 'Rose', 'Anna',        'Julia', 'Clara', 'Emma', 'Yasmine', 'Estelle', 'Aurore',        'Constance', 'Diane', 'Éléonore', 'Fanny', 'Gabrielle',        'Hélène', 'Iris', 'Jeanne', 'Maëva', 'Noémie', 'Ophélie',        'Solène', 'Valentine', 'Priscilla', 'Tiffany', 'Leslie',    ];    private const CODES = ['3615', '3617'];    private const PROFILES = [        [            '..............', '....HHHHHH....', '...HHHHHHHH...',            '..HHHHHHHHHH..', '..HHHHHHHHHH..', '..HHFFFFFFHH..',            '..HFFEFFEFFH..', '..HFFFFFFFFH..', '..HFFFMMFFFH..',            '..HFFFFFFFFH..', '...FFFFFFFF...', '....FFFFFF....',            '.....FFFF.....', '..............',        ],        [            '..............', '....HHHHHH....', '...HHHHHHHH...',            '..HHHHHHHHHH..', '..HHHHHHHHHH..', '..HHFFFFFFHH..',            '..HFFEFFEFFH..', '..HFFFFFFFFH..', '..HHFFMMFFHH..',            '..HHFFFFFFHH..', '..HHFFFFFFHH..', '..HHFFFFFFHH..',            '...HHFFFFHH...', '....HHHHHH....',        ],        [            '......HH......', '......HH......', '....HHHHHH....',            '...HHHHHHHH...', '..HHHHHHHHHH..', '..HHHHHHHHHH..',            '..HHFFFFFFHH..', '..HFFEFFEFFH..', '..HFFFFFFFFH..',            '..HFFFMMFFFH..', '..HFFFFFFFFH..', '...FFFFFFFF...',            '....FFFFFF....', '..............',        ],        [            '.H.H.H.H.H.H..', 'HHHHHHHHHHHH..', '.HHHHHHHHHH...',            'HHHHHHHHHHHH..', 'HHFFFFFFFFHH..', 'HHFEFFFFEFHH..',            '.HFFFFFFFFH...', '.HFFFFFFFFH...', '.HFFFMMFFFH...',            '.HFFFFFFFFH...', '..HFFFFFFH....', '...FFFFFF.....',            '....FFFF......', '..............',        ],        [            '..............', '....HHHHHH....', '...HHHHHHHH...',            '..HHHHHHHHHH..', '..HHHHHHHHHH..', '..HHFFFFFFH...',            '..HFFEFFEFFH..', '..HFFFFFFFFH..', '..HFFFMMFFFH..',            '..HFFFFFFFFH..', '...FFFFFFFF..H', '....FFFFFFHHHH',            '.....FFFFHHHHH', '..........HHHH',        ],        [            '..............', '....HHHHHH....', '...HHHHHHHH...',            '..HHHHHHHHHH..', '..HHHHHHHHHH..', '..HHFFFFFFHH..',            '..HFFEFFEFFH..', '..HFFFFFFFFH..', '..HFFFFFFFFH..',            '..HHFFMMFFHH..', '..HHFFFFFFHH..', '..HHFFFFFFHH..',            '..HHFFFFFFHH..', '..HHFFFFFFHH..',        ],        [            '..............', '....HHHHHH....', 'H..HHHHHHHH..H',            'HH.HHHHHHHH.HH', 'HHHHHHHHHHHHHH', '.HHFFFFFFFFHH.',            '.HHFEFFFFEFHH.', '.HHFFFFFFFFHH.', '..HFFFFFFFFH..',            '..HFFFMMFFFH..', '..HFFFFFFFFH..', '...FFFFFFFF...',            '....FFFFFF....', '.....FFFF.....',        ],        [            '..............', '..............', '....HHHHHH....',            '...HHHHHHHH...', '..HHHHHHHHHH..', '..HHHHHHHHHH..',            '..HHFFFFFFHH..', '..HFFEFFEFFH..', '..HFFFFFFFFH..',            '..HFFFMMFFFH..', '..HFFFFFFFFH..', '...FFFFFFFF...',            '....FFFFFF....', '.....FFFF.....',        ],        [            '..H..H..H..H..', '.HHH.HHH.HHH..', 'HHHHHHHHHHHHH.',            'HHHHHHHHHHHHHH', 'HHHHHHHHHHHHH.', '.HHFFFFFFFFHH.',            '..HFEFFFFEFH..', '..HFFFFFFFFH..', '..HFFFFFFFFH..',            '..HFFFMMFFFH..', '..HFFFFFFFFH..', '...FFFFFFFF...',            '....FFFFFF....', '.....FFFF.....',        ],        [            '..............', '....HHHHHH....', '...HHHHHHHH...',            '..HHHHHHHHHH..', '..HHBBBBBBHH..', '..HHFFFFFFHH..',            '..HFFEFFEFFH..', '..HFFFFFFFFH..', '..HFFFMMFFFH..',            '..HFFFFFFFFH..', '...FFFFFFFF...', '....FFFFFF....',            '.....FFFF.....', '..............',        ],    ];    private const SKIN_COLORS = [        [0xF8, 0xD5, 0xC0], [0xF0, 0xB8, 0xA0], [0xE8, 0xA8, 0x88],        [0xD8, 0x90, 0x70], [0xFC, 0xE0, 0xD0], [0xF5, 0xC8, 0xB0],    ];    private const HAIR_COLORS = [        [0xE8, 0xC8, 0x70], [0xE8, 0xE0, 0xD0], [0x7A, 0x50, 0x30],        [0x2B, 0x20, 0x20], [0xA0, 0x40, 0x20], [0xFF, 0x90, 0xB0],        [0xD0, 0x80, 0x70],    ];    private const TAGLINES = [        'vous attend en direct',        'messagerie rose 24/24',        'rencontres coquines',        'en direct sur minitel',        'tapez vite!',    ];    public const WIDTH = 400;    public const HEIGHT = 580;    private const PIXEL_SIZE = 20;    private const PORTRAIT_W = 280;    private const PORTRAIT_H = 280;    private const COLOR_BG = [0x10, 0x08, 0x18];    private const COLOR_PINK = [0xFF, 0x60, 0xA0];    private const COLOR_BRIGHT_PINK = [0xFF, 0x90, 0xC0];    private const COLOR_HOT_PINK = [0xFF, 0x40, 0x80];    private const COLOR_EYE = [0x00, 0x00, 0x00];    private const COLOR_MOUTH = [0x90, 0x20, 0x30];    public function __construct()    {        if (!extension_loaded('gd')) {            throw new \RuntimeException('ext-gd est requis pour MinitelRosePoster.');        }    }    public function generate(?int $seed = null): \GdImage    {        if ($seed !== null) mt_srand($seed);        return $this->render(            self::NAMES[array_rand(self::NAMES)],            self::CODES[array_rand(self::CODES)],            self::PROFILES[array_rand(self::PROFILES)],            self::SKIN_COLORS[array_rand(self::SKIN_COLORS)],            self::HAIR_COLORS[array_rand(self::HAIR_COLORS)],            self::TAGLINES[array_rand(self::TAGLINES)],        );    }    public function generateAndSave(string $path, ?int $seed = null): string    {        $img = $this->generate($seed);        $dir = dirname($path);        if (!is_dir($dir)) mkdir($dir, 0755, true);        imagepng($img, $path);        imagedestroy($img);        return $path;    }    public function generateBatch(string $dir, int $count = 20, int $startSeed = 0): array    {        if (!is_dir($dir)) mkdir($dir, 0755, true);        $paths = [];        for ($i = 0; $i < $count; $i++) {            $seed = $startSeed + $i;            $path = rtrim($dir, '/') . "/poster_{$seed}.png";            $this->generateAndSave($path, $seed);            $paths[] = $path;        }        return $paths;    }    private function render(string $name, string $code, array $profile, array $skin, array $hair, string $tagline): \GdImage    {        $img = imagecreatetruecolor(self::WIDTH, self::HEIGHT);        imagesavealpha($img, true);        imagealphablending($img, true);        $bg = $this->alloc($img, self::COLOR_BG);        imagefill($img, 0, 0, $bg);        $this->drawScanlines($img);        $this->drawTextScaled($img, '* MINITEL ROSE *', intdiv(self::WIDTH, 2), 15, self::COLOR_BRIGHT_PINK, 1, true);        $px = self::PIXEL_SIZE;        $pxX = intdiv(self::WIDTH - self::PORTRAIT_W, 2);        $pxY = 50;        $this->drawPortrait($img, $profile, $pxX, $pxY, $px, $skin, $hair);        $frame = $this->alloc($img, self::COLOR_PINK);        imagerectangle($img, $pxX - 2, $pxY - 2, $pxX + self::PORTRAIT_W + 1, $pxY + self::PORTRAIT_H + 1, $frame);        $nameL1 = $this->toLatin1($name);        $nameW = imagefontwidth(5) * strlen($nameL1);        $nameScale = max(2, min(4, intdiv(self::WIDTH - 40, max(1, $nameW))));        $this->drawTextScaled($img, $nameL1, intdiv(self::WIDTH, 2), 350, self::COLOR_BRIGHT_PINK, $nameScale, true);        $this->drawTextScaled($img, $tagline, intdiv(self::WIDTH, 2), 420, self::COLOR_PINK, 1, true);        $codeLine = $code . ' ' . $nameL1;        $codeW = imagefontwidth(5) * strlen($codeLine);        $codeScale = max(2, min(3, intdiv(self::WIDTH - 40, max(1, $codeW))));        $this->drawTextScaled($img, $codeLine, intdiv(self::WIDTH, 2), 455, self::COLOR_HOT_PINK, $codeScale, true);        $this->drawTextScaled($img, '* * *', intdiv(self::WIDTH, 2), 540, self::COLOR_BRIGHT_PINK, 1, true);        return $img;    }    private function drawPortrait(\GdImage $img, array $profile, int $x, int $y, int $px, array $skin, array $hair): void    {        $cHair = $this->alloc($img, $hair);        $cSkin = $this->alloc($img, $skin);        $cEye = $this->alloc($img, self::COLOR_EYE);        $cMouth = $this->alloc($img, self::COLOR_MOUTH);        $cBand = $this->alloc($img, self::COLOR_HOT_PINK);        foreach ($profile as $r => $row) {            for ($c = 0, $len = strlen($row); $c < $len; $c++) {                $col = match ($row[$c]) {                    'H' => $cHair, 'F' => $cSkin, 'E' => $cEye, 'M' => $cMouth, 'B' => $cBand, default => null,                };                if ($col === null) continue;                imagefilledrectangle($img, $x + $c * $px, $y + $r * $px, $x + ($c + 1) * $px - 1, $y + ($r + 1) * $px - 1, $col);            }        }    }    private function drawTextScaled(\GdImage $img, string $text, int $cx, int $cy, array $color, int $scale, bool $center): void    {        $fw = imagefontwidth(5);        $fh = imagefontheight(5);        $tw = $fw * strlen($text);        if ($tw === 0) return;        $tmp = imagecreatetruecolor($tw, $fh);        $white = imagecolorallocate($tmp, 255, 255, 255);        $black = imagecolorallocate($tmp, 0, 0, 0);        imagefill($tmp, 0, 0, $white);        imagestring($tmp, 5, 0, 0, $text, $black);        $dw = $tw * $scale;        $dx = $center ? $cx - intdiv($dw, 2) : $cx;        $fill = $this->alloc($img, $color);        for ($y = 0; $y < $fh; $y++) {            for ($x = 0; $x < $tw; $x++) {                if ((imagecolorat($tmp, $x, $y) & 0xFF) < 128) {                    imagefilledrectangle($img, $dx + $x * $scale, $cy + $y * $scale, $dx + ($x + 1) * $scale - 1, $cy + ($y + 1) * $scale - 1, $fill);                }            }        }        imagedestroy($tmp);    }    private function drawScanlines(\GdImage $img): void    {        $dark = imagecolorallocatealpha($img, 0, 0, 0, 55);        for ($y = 0; $y < self::HEIGHT; $y += 3) imageline($img, 0, $y, self::WIDTH - 1, $y, $dark);    }    private function alloc(\GdImage $img, array $rgb): int    {        return imagecolorallocate($img, $rgb[0], $rgb[1], $rgb[2]);    }    private function toLatin1(string $s): string    {        if (function_exists('mb_convert_encoding')) {            $r = @mb_convert_encoding($s, 'ISO-8859-1', 'UTF-8');            if (is_string($r)) return $r;        }        if (function_exists('iconv')) {            $r = @iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $s);            if (is_string($r)) return $r;        }        return strtr($s, [            'é'=>'e','è'=>'e','ê'=>'e','ë'=>'e','à'=>'a','â'=>'a','ä'=>'a',            'î'=>'i','ï'=>'i','ô'=>'o','ö'=>'o','ù'=>'u','û'=>'u','ü'=>'u',            'ç'=>'c','É'=>'E','È'=>'E','Ê'=>'E','Ë'=>'E','À'=>'A','Â'=>'A',            'Ä'=>'A','Î'=>'I','Ï'=>'I','Ô'=>'O','Ö'=>'O','Ù'=>'U','Û'=>'U',            'Ü'=>'U','Ç'=>'C','œ'=>'oe','æ'=>'ae','Œ'=>'OE','Æ'=>'AE',        ]);    }}