Add multiplayer rooms and Vercel analytics

This commit is contained in:
2026-10-07 12:28:45 +05:00
parent ce26e6fdbc
commit 48b34db6f1
20 changed files with 2345 additions and 9 deletions

View File

@@ -43,6 +43,93 @@ export type Database = {
};
public: {
Tables: {
multiplayer_rooms: {
Row: {
id: string;
revision_id: string;
host_player_id: string;
difficulty: string;
player_limit: number;
status: string;
current_round: number;
transition_at: string | null;
created_at: string;
};
Insert: {
id?: string;
revision_id: string;
host_player_id: string;
difficulty: string;
player_limit: number;
status?: string;
current_round?: number;
transition_at?: string | null;
created_at?: string;
};
Update: {
host_player_id?: string;
status?: string;
current_round?: number;
transition_at?: string | null;
};
Relationships: [];
};
multiplayer_rounds: {
Row: {
room_id: string;
round_number: number;
viewpoint: Json;
deadline: string | null;
closed_at: string | null;
};
Insert: {
room_id: string;
round_number: number;
viewpoint: Json;
deadline?: string | null;
closed_at?: string | null;
};
Update: { deadline?: string | null; closed_at?: string | null };
Relationships: [];
};
multiplayer_players: {
Row: {
id: string;
room_id: string;
access_hash: string;
display_name: string;
ready_round: number;
has_left: boolean;
joined_at: string;
};
Insert: {
id?: string;
room_id: string;
access_hash: string;
display_name: string;
ready_round?: number;
has_left?: boolean;
joined_at?: string;
};
Update: { ready_round?: number; has_left?: boolean };
Relationships: [];
};
multiplayer_guesses: {
Row: {
room_id: string;
player_id: string;
round_number: number;
result: Json;
};
Insert: {
room_id: string;
player_id: string;
round_number: number;
result: Json;
};
Update: { result?: Json };
Relationships: [];
};
site_settings: {
Row: {
id: boolean;
@@ -540,6 +627,25 @@ export type Database = {
[_ in never]: never;
};
Functions: {
create_multiplayer_room: {
Args: {
p_dataset: string;
p_difficulty: string;
p_name: string;
p_limit: number;
p_access_hash: string;
};
Returns: string;
};
multiplayer_action: {
Args: {
p_room: string;
p_access_hash: string;
p_action: string;
p_payload: Json;
};
Returns: Json;
};
legacy_password_needed: { Args: { p_user: string }; Returns: boolean };
claim_legacy_password: { Args: { p_user: string }; Returns: boolean };
set_beta_badge: {

View File

@@ -605,5 +605,64 @@
"Not now": "Не сейчас",
"Select the password and copy it manually.": "Выдели пароль и скопируй его вручную.",
"A password is already set or was already issued. Use password recovery if needed.": "Пароль уже установлен или был выдан ранее. Если нужно, воспользуйся восстановлением пароля.",
"Password setup could not be confirmed. Use password recovery before signing out.": "Не удалось подтвердить установку пароля. Восстанови пароль перед выходом из аккаунта."
"Password setup could not be confirmed. Use password recovery before signing out.": "Не удалось подтвердить установку пароля. Восстанови пароль перед выходом из аккаунта.",
"Game mode": "Режим игры",
"Solo": "Одиночная игра",
"Multiplayer": "Мультиплеер",
"Your player name": "Ваше игровое имя",
"Enter a player name": "Введите игровое имя",
"Player limit (including you)": "Лимит игроков (включая вас)",
"Create a room, share the invite link, and start when your friends arrive.": "Создайте комнату, поделитесь ссылкой и начните игру, когда друзья присоединятся.",
"Create multiplayer room": "Создать комнату",
"Retry": "Повторить",
"Loading multiplayer room…": "Загрузка комнаты…",
"Final standings": "Итоговая таблица",
"This room is closed": "Эта комната закрыта",
"An expedition with friends.": "Экспедиция с друзьями.",
"Join your friends for five rounds on the same map.": "Присоединитесь к друзьям: пять раундов на одной карте.",
"{count} / {limit} players": "Игроков: {count} / {limit}",
"This room is full": "Комната заполнена",
"Join room": "Войти в комнату",
"This match has already started": "Игра уже началась",
"Share the invite link. Start when your friends arrive.": "Поделитесь ссылкой. Начните игру, когда друзья присоединятся.",
"You’re in! Waiting for the room owner to start.": "Вы в комнате! Ждём, когда владелец начнёт игру.",
"Invite link": "Ссылка-приглашение",
"Copied!": "Скопировано!",
"Copy link": "Копировать ссылку",
"Room owner": "Владелец комнаты",
"(you)": "(вы)",
"Remove {name}": "Удалить игрока {name}",
"Remove": "Удалить",
"Everyone plays the same five locations. The most points wins; equal totals share a place.": "Все играют на одинаковых пяти локациях. Побеждает игрок с наибольшим числом очков; при равном счёте место общее.",
"Start multiplayer match": "Начать игру",
"Leave room": "Покинуть комнату",
"Close room": "Закрыть комнату",
"Invite at least one friend before starting": "Перед началом пригласите хотя бы одного друга",
"{count} points": "{count} очков",
"Five-round totals": "Результаты пяти раундов",
"Place": "Место",
"Player": "Игрок",
"Total points": "Всего очков",
"Left room": "Покинул комнату",
"Play this map again": "Сыграть на этой карте ещё раз",
"Loading…": "Загрузка…",
"Players": "Игроки",
"Ready": "Готов",
"Guess submitted": "Ответ отправлен",
"Guessing": "Выбирает место",
"Guess locked. Waiting for the other players.": "Ответ принят. Ждём остальных игроков.",
"Time is up. Calculating round scores…": "Время вышло. Подсчитываем очки…",
"Waiting for players to load the shared round…": "Ждём загрузки раунда у остальных игроков…",
"Starting within {seconds}s": "Начало не позднее чем через {seconds} с",
"Round standings": "Результаты раунда",
"Next round in {seconds}s": "Следующий раунд через {seconds} с",
"Copy the invite link from the field below": "Скопируйте ссылку из поля ниже",
"Choose between 2 and 16 players": "Выберите от 2 до 16 игроков",
"This name is already taken in this room": "Это имя уже занято в комнате",
"Room owner permission required": "Это действие доступно владельцу комнаты",
"Room membership permission required": "Сначала присоединитесь к комнате",
"Room not found": "Комната не найдена",
"Join this room to play": "Присоединитесь к комнате, чтобы играть",
"Wait for the round to finish": "Дождитесь завершения раунда",
"Players can only be removed before the match starts": "Удалять игроков можно только до начала игры"
}

62
shared/multiplayer.ts Normal file
View File

@@ -0,0 +1,62 @@
import { z } from "zod";
import { displayNameSchema } from "./account";
import type { Bounds, Difficulty } from "./protocol";
export const createRoomSchema = z.object({
datasetId: z.string().uuid(),
difficulty: z.enum(["easy", "normal", "hard"]),
displayName: displayNameSchema,
playerLimit: z.number().int().min(2).max(16),
});
export const joinRoomSchema = z.object({ displayName: displayNameSchema });
export const roomRoundSchema = z.object({
round: z.number().int().min(0).max(4),
});
export const roomGuessSchema = roomRoundSchema.extend({
x: z.number().finite(),
z: z.number().finite(),
});
export type RoomResult = {
playerId: string;
score: number;
distance: number | null;
timedOut: boolean;
actual: { x: number; z: number };
guess: { x: number; z: number } | null;
};
export type RoomView = {
id: string;
datasetId: string;
name: string;
difficulty: Difficulty;
playerLimit: number;
playerCount: number;
status: "lobby" | "loading" | "playing" | "results" | "finished" | "closed";
round: number;
hostId: string;
you: string | null;
serverNow: number;
transitionAt: string | null;
players: {
id: string;
name: string;
left: boolean;
ready: boolean;
submitted: boolean;
total: number;
rank: number;
}[];
rounds: {
number: number;
deadline: string | null;
closedAt: string | null;
results: RoomResult[];
}[];
myGuess: { x: number; z: number } | null;
};
export type RoomMedia = {
round: number;
map: string;
bounds: Bounds;
faces: string[];
};