$objects enemies (tile column, row) */ private function __construct( public readonly int $number, public readonly Island $island, public readonly int $top, public readonly int $goalRow, public readonly int $hurrySeconds, public readonly array $signs, public readonly array $objects, private array $rows, ) { } public static function load(string $file, Island $island): self { $data = json_decode((string)file_get_contents($file), true); $rows = []; foreach ($data['rows'] as $hex) { $rows[] = array_values(unpack('C*', (string)hex2bin($hex))); } return new self($data['round'], $island, $data['top'], $data['goalRow'], $data['hurry'], $data['signs'], $data['objects'], $rows); } /** The last round of an island: a boss waits above the goal. */ public function isBossRound(): bool { return $this->number % 4 === 0; } public function topY(): int { return $this->top * self::TILE; } public function goalY(): int { return $this->goalRow * self::TILE; } /** The tile byte at a column and an (absolute, negative) row; 0 outside the map. */ public function tile(int $column, int $row): int { return $this->rows[$row - $this->top][$column] ?? 0; } public function isSolid(int $column, int $row): bool { if ($column < 0 || $column >= self::COLUMNS || $row < $this->top || $row >= 0) { return false; } return ($this->tile($column, $row) & 0x7f) >= $this->island->solidFrom; } /** True when the top edge of this tile can be stood on: platforms are crossed from below. */ public function isGround(int $column, int $row): bool { return $this->isSolid($column, $row) && !$this->isSolid($column, $row - 1); } /** @return int[][] */ public function rows(): array { return $this->rows; } }