Take a look at "Mutex" functionality or so called locks.
At the company where I work we had kind a similar problem where a PDF was generated based on a user action. The PDF was generated in a worker and because it was a "heavy" task (PDF generation + upload), if two or more users triggered this action at the same time, there was a possibility that the generated PDF document would not be up to date.
We didn't wanted the workers to be executed synchronously or to have a dedicated queue for this job, so instead we implemented a Mutex just in that job. The Mutex would have the UUID of the PDF as key, so every time a PDF is generated we would first check if the same PDF is already being generated. Since the key was the UUID of the PDF, other (different) PDF documents would not be affected by this Mutex. Additionally we found a great Mutex implementation that works with Redis which allowed us to be more scalable.
I think that you can solve your problem in this very same way. You would have the whole logic of joining a game room inside a "mutex" which will be identified with the ID of the room. Other rooms would not be affected and joining a game room would be done synchronously.
The PHP library for mutex that we use: https://github.com/php-lock/lock
Example with Redis: https://github.com/php-lock/lock#phpredismutex
If you already use Redis as a queue driver for the workers, you can use the same Redis host for your mutex.
Your code should probably look something like (I recommend a dedicated Redis instance, not the localhost):
$redis = new Client("redis://localhost");
$mutex = new PredisMutex([$redis], $room->id);
$mutex->synchronized(function () use ($your_params ...) {
// logic for joining a room ...
});