actorRepository->findOneRandom(4); $game = new Game(); $game->setMainActor($mainActor); $game->setUser($user); $usedActors = [$mainActor->getId()]; $rowOrder = 0; $usedMovieRoleIds = []; $usedHintKeys = []; foreach (str_split(strtolower($mainActor->getName())) as $char) { if (!preg_match('/[a-z]/', $char)) { continue; } $tryFindActor = 0; do { $actor = $this->actorRepository->findOneRandom(4, $char); ++$tryFindActor; } while ( in_array($actor->getId(), $usedActors) || $tryFindActor < 5 ); $usedActors[] = $actor->getId(); $row = new GameRow(); $row->setActor($actor); $row->setPosition(strpos(strtolower($actor->getName()), $char)); $row->setRowOrder($rowOrder); $hint = $this->generateHint($mainActor, $usedMovieRoleIds, $usedHintKeys); if ($hint !== null) { $row->setHintType($hint['type']); $row->setHintData($hint['data']); } $game->addRow($row); ++$rowOrder; } $this->em->persist($game); $this->em->flush(); return $game; } /** * Compute display data (grid, width, middle) from a Game entity for the React component. * * @return array{grid: list, width: int, middle: int} */ public function computeGridData(Game $game): array { $leftSize = 0; $rightSize = 0; $grid = []; $mainActorChars = str_split($game->getMainActor()->getName()); $rows = $game->getRows()->toArray(); $rowIndex = 0; foreach ($mainActorChars as $char) { if (!preg_match('/[a-zA-Z]/', $char)) { $grid[] = [ 'separator' => $char, ]; continue; } $row = $rows[$rowIndex] ?? null; ++$rowIndex; if ($row === null) { continue; } $actor = $row->getActor(); $pos = $row->getPosition(); if ($leftSize < $pos) { $leftSize = $pos; } $rightSizeActor = strlen($actor->getName()) - $pos - 1; if ($rightSize < $rightSizeActor) { $rightSize = $rightSizeActor; } $grid[] = [ 'actorName' => $actor->getName(), 'actorId' => $actor->getId(), 'pos' => $pos, ]; } return [ 'grid' => $grid, 'width' => $rightSize + $leftSize + 1, 'middle' => $leftSize, ]; } /** * @param list $usedMovieRoleIds MovieRole IDs already used (for DB exclusion) * @param list $usedHintKeys Semantic keys like "film:42" to avoid duplicate hints * @return array{type: string, data: string}|null */ private function generateHint(Actor $mainActor, array &$usedMovieRoleIds, array &$usedHintKeys): ?array { $types = ['film', 'character', 'award']; shuffle($types); foreach ($types as $type) { $hint = $this->resolveHint($type, $mainActor, $usedMovieRoleIds, $usedHintKeys); if ($hint !== null) { return $hint; } } return null; } /** * @param list $usedMovieRoleIds * @param list $usedHintKeys * @return array{type: string, data: string}|null */ private function resolveHint(string $type, Actor $mainActor, array &$usedMovieRoleIds, array &$usedHintKeys): ?array { switch ($type) { case 'film': $role = $this->movieRoleRepository->findOneRandomByActor( $mainActor->getId(), $usedMovieRoleIds, ); if ($role === null) { return null; } $movieId = (string) $role->getMovie()->getId(); $key = 'film:' . $movieId; if (in_array($key, $usedHintKeys)) { return null; } $usedMovieRoleIds[] = $role->getId(); $usedHintKeys[] = $key; return ['type' => 'film', 'data' => $movieId]; case 'character': $role = $this->movieRoleRepository->findOneRandomByActor( $mainActor->getId(), $usedMovieRoleIds, ); if ($role === null) { return null; } $roleId = (string) $role->getId(); $key = 'character:' . $roleId; if (in_array($key, $usedHintKeys)) { return null; } $usedMovieRoleIds[] = $role->getId(); $usedHintKeys[] = $key; return ['type' => 'character', 'data' => $roleId]; case 'award': try { $awards = $this->wikidataAwardGateway->getAwards($mainActor); } catch (\Throwable) { return null; } foreach ($awards as $award) { $text = $award['name'] . ' (' . $award['year'] . ')'; $key = 'award:' . $text; if (!in_array($key, $usedHintKeys)) { $usedHintKeys[] = $key; return ['type' => 'award', 'data' => $text]; } } return null; } return null; } }