@@ -57,12 +57,14 @@ function startGame(roomId: string): GameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
// @ts-ignore
|
// @ts-expect-error - NextRequest extended with socket server
|
||||||
const res = req.res || req.nextUrl;
|
const res = req.res || req.nextUrl;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
if (!(res as any).socket?.server?.io) {
|
if (!(res as any).socket?.server?.io) {
|
||||||
console.log('Initializing Socket.IO server...');
|
console.log('Initializing Socket.IO server...');
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const httpServer: HTTPServer = (res as any).socket.server;
|
const httpServer: HTTPServer = (res as any).socket.server;
|
||||||
const io = new SocketIOServer(httpServer, {
|
const io = new SocketIOServer(httpServer, {
|
||||||
path: '/api/socket',
|
path: '/api/socket',
|
||||||
@@ -167,7 +169,7 @@ export async function GET(req: NextRequest) {
|
|||||||
|
|
||||||
gameRooms.set(roomId, newGameState);
|
gameRooms.set(roomId, newGameState);
|
||||||
io.to(roomId).emit('game-state-updated', newGameState);
|
io.to(roomId).emit('game-state-updated', newGameState);
|
||||||
} catch (error) {
|
} catch {
|
||||||
socket.emit('invalid-move', 'Invalid move');
|
socket.emit('invalid-move', 'Invalid move');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -220,6 +222,7 @@ export async function GET(req: NextRequest) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(res as any).socket.server.io = io;
|
(res as any).socket.server.io = io;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useRef, useEffect, useState } from 'react';
|
import React, { useRef, useEffect, useState, useMemo } from 'react';
|
||||||
import { PlacedTile, Position } from '@/lib/types';
|
import { PlacedTile, Position } from '@/lib/types';
|
||||||
|
|
||||||
interface GameBoardProps {
|
interface GameBoardProps {
|
||||||
@@ -17,7 +17,7 @@ export function GameBoard({ placedTiles, width = 1200, height = 700, className =
|
|||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const [dragStart, setDragStart] = useState<Position>({ x: 0, y: 0 });
|
const [dragStart, setDragStart] = useState<Position>({ x: 0, y: 0 });
|
||||||
const [canvasSize, setCanvasSize] = useState({ width, height });
|
const [canvasSize, setCanvasSize] = useState({ width, height });
|
||||||
const [zoom, setZoom] = useState(1);
|
const initialZoom = 1;
|
||||||
|
|
||||||
// Handle responsive canvas sizing
|
// Handle responsive canvas sizing
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -37,8 +37,8 @@ export function GameBoard({ placedTiles, width = 1200, height = 700, className =
|
|||||||
}, [width, height]);
|
}, [width, height]);
|
||||||
|
|
||||||
// Auto-zoom and auto-center to fit all tiles
|
// Auto-zoom and auto-center to fit all tiles
|
||||||
useEffect(() => {
|
const autoFitValues = useMemo(() => {
|
||||||
if (placedTiles.length === 0) return;
|
if (placedTiles.length === 0) return null;
|
||||||
|
|
||||||
// Calculate bounding box of all tiles
|
// Calculate bounding box of all tiles
|
||||||
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
||||||
@@ -65,7 +65,7 @@ export function GameBoard({ placedTiles, width = 1200, height = 700, className =
|
|||||||
const contentHeight = maxY - minY;
|
const contentHeight = maxY - minY;
|
||||||
|
|
||||||
// Calculate zoom to fit all tiles in viewport
|
// Calculate zoom to fit all tiles in viewport
|
||||||
const isMobile = window.innerWidth < 640;
|
const isMobile = typeof window !== 'undefined' && window.innerWidth < 640;
|
||||||
const zoomX = canvasSize.width / contentWidth;
|
const zoomX = canvasSize.width / contentWidth;
|
||||||
const zoomY = canvasSize.height / contentHeight;
|
const zoomY = canvasSize.height / contentHeight;
|
||||||
|
|
||||||
@@ -83,10 +83,13 @@ export function GameBoard({ placedTiles, width = 1200, height = 700, className =
|
|||||||
const newOffsetX = canvasSize.width / 2 - centerX * newZoom;
|
const newOffsetX = canvasSize.width / 2 - centerX * newZoom;
|
||||||
const newOffsetY = canvasSize.height / 2 - centerY * newZoom;
|
const newOffsetY = canvasSize.height / 2 - centerY * newZoom;
|
||||||
|
|
||||||
setZoom(newZoom);
|
return { zoom: newZoom, offset: { x: newOffsetX, y: newOffsetY } };
|
||||||
setOffset({ x: newOffsetX, y: newOffsetY });
|
|
||||||
}, [placedTiles, canvasSize.width, canvasSize.height]);
|
}, [placedTiles, canvasSize.width, canvasSize.height]);
|
||||||
|
|
||||||
|
// Derive effective zoom and offset values
|
||||||
|
const effectiveZoom = autoFitValues?.zoom ?? initialZoom;
|
||||||
|
const effectiveOffset = autoFitValues?.offset ?? offset;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
if (!canvas) return;
|
if (!canvas) return;
|
||||||
@@ -118,27 +121,27 @@ export function GameBoard({ placedTiles, width = 1200, height = 700, className =
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw placed tiles
|
// Draw placed tiles
|
||||||
placedTiles.forEach((placedTile, index) => {
|
placedTiles.forEach((placedTile) => {
|
||||||
const { tile, position, orientation } = placedTile;
|
const { tile, position, orientation } = placedTile;
|
||||||
const tileWidth = orientation === 'horizontal' ? 60 : 30;
|
const tileWidth = orientation === 'horizontal' ? 60 : 30;
|
||||||
const tileHeight = orientation === 'horizontal' ? 30 : 60;
|
const tileHeight = orientation === 'horizontal' ? 30 : 60;
|
||||||
|
|
||||||
const x = position.x * zoom + offset.x;
|
const x = position.x * effectiveZoom + effectiveOffset.x;
|
||||||
const y = position.y * zoom + offset.y;
|
const y = position.y * effectiveZoom + effectiveOffset.y;
|
||||||
const scaledWidth = tileWidth * zoom;
|
const scaledWidth = tileWidth * effectiveZoom;
|
||||||
const scaledHeight = tileHeight * zoom;
|
const scaledHeight = tileHeight * effectiveZoom;
|
||||||
|
|
||||||
// Draw tile background with shadow
|
// Draw tile background with shadow
|
||||||
ctx.shadowColor = 'rgba(0, 0, 0, 0.2)';
|
ctx.shadowColor = 'rgba(0, 0, 0, 0.2)';
|
||||||
ctx.shadowBlur = 5 * zoom;
|
ctx.shadowBlur = 5 * effectiveZoom;
|
||||||
ctx.shadowOffsetX = 2 * zoom;
|
ctx.shadowOffsetX = 2 * effectiveZoom;
|
||||||
ctx.shadowOffsetY = 2 * zoom;
|
ctx.shadowOffsetY = 2 * effectiveZoom;
|
||||||
|
|
||||||
ctx.fillStyle = '#ffffff';
|
ctx.fillStyle = '#ffffff';
|
||||||
ctx.strokeStyle = '#1f2937';
|
ctx.strokeStyle = '#1f2937';
|
||||||
ctx.lineWidth = 2 * zoom;
|
ctx.lineWidth = 2 * effectiveZoom;
|
||||||
|
|
||||||
const radius = 4 * zoom;
|
const radius = 4 * effectiveZoom;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(x + radius, y);
|
ctx.moveTo(x + radius, y);
|
||||||
ctx.lineTo(x + scaledWidth - radius, y);
|
ctx.lineTo(x + scaledWidth - radius, y);
|
||||||
@@ -158,7 +161,7 @@ export function GameBoard({ placedTiles, width = 1200, height = 700, className =
|
|||||||
|
|
||||||
// Draw center divider
|
// Draw center divider
|
||||||
ctx.strokeStyle = '#6b7280';
|
ctx.strokeStyle = '#6b7280';
|
||||||
ctx.lineWidth = 1 * zoom;
|
ctx.lineWidth = 1 * effectiveZoom;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
if (orientation === 'horizontal') {
|
if (orientation === 'horizontal') {
|
||||||
ctx.moveTo(x + scaledWidth / 2, y);
|
ctx.moveTo(x + scaledWidth / 2, y);
|
||||||
@@ -170,7 +173,7 @@ export function GameBoard({ placedTiles, width = 1200, height = 700, className =
|
|||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
// Draw dots
|
// Draw dots
|
||||||
const dotRadius = Math.max(2, 2.5 * zoom);
|
const dotRadius = Math.max(2, 2.5 * effectiveZoom);
|
||||||
ctx.fillStyle = '#1f2937';
|
ctx.fillStyle = '#1f2937';
|
||||||
|
|
||||||
const drawDots = (value: number, dotX: number, dotY: number, size: number) => {
|
const drawDots = (value: number, dotX: number, dotY: number, size: number) => {
|
||||||
@@ -186,7 +189,7 @@ export function GameBoard({ placedTiles, width = 1200, height = 700, className =
|
|||||||
const leftX = x + scaledWidth / 4;
|
const leftX = x + scaledWidth / 4;
|
||||||
const rightX = x + (scaledWidth * 3) / 4;
|
const rightX = x + (scaledWidth * 3) / 4;
|
||||||
const centerY = y + scaledHeight / 2;
|
const centerY = y + scaledHeight / 2;
|
||||||
const dotAreaSize = (scaledWidth / 2 - 6 * zoom);
|
const dotAreaSize = (scaledWidth / 2 - 6 * effectiveZoom);
|
||||||
|
|
||||||
drawDots(tile.left, leftX, centerY, dotAreaSize);
|
drawDots(tile.left, leftX, centerY, dotAreaSize);
|
||||||
drawDots(tile.right, rightX, centerY, dotAreaSize);
|
drawDots(tile.right, rightX, centerY, dotAreaSize);
|
||||||
@@ -194,14 +197,14 @@ export function GameBoard({ placedTiles, width = 1200, height = 700, className =
|
|||||||
const topY = y + scaledHeight / 4;
|
const topY = y + scaledHeight / 4;
|
||||||
const bottomY = y + (scaledHeight * 3) / 4;
|
const bottomY = y + (scaledHeight * 3) / 4;
|
||||||
const centerX = x + scaledWidth / 2;
|
const centerX = x + scaledWidth / 2;
|
||||||
const dotAreaSize = (scaledHeight / 2 - 6 * zoom);
|
const dotAreaSize = (scaledHeight / 2 - 6 * effectiveZoom);
|
||||||
|
|
||||||
drawDots(tile.left, centerX, topY, dotAreaSize);
|
drawDots(tile.left, centerX, topY, dotAreaSize);
|
||||||
drawDots(tile.right, centerX, bottomY, dotAreaSize);
|
drawDots(tile.right, centerX, bottomY, dotAreaSize);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}, [placedTiles, offset, zoom, canvasSize.width, canvasSize.height]);
|
}, [placedTiles, effectiveOffset, effectiveZoom, canvasSize.width, canvasSize.height]);
|
||||||
|
|
||||||
const handleMouseDown = (e: React.MouseEvent<HTMLCanvasElement>) => {
|
const handleMouseDown = (e: React.MouseEvent<HTMLCanvasElement>) => {
|
||||||
setIsDragging(true);
|
setIsDragging(true);
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ interface LobbyProps {
|
|||||||
onCreateRoom: (playerName: string) => void;
|
onCreateRoom: (playerName: string) => void;
|
||||||
onJoinRoom: (roomId: string, playerName: string) => void;
|
onJoinRoom: (roomId: string, playerName: string) => void;
|
||||||
onStartAI: (playerName: string) => void;
|
onStartAI: (playerName: string) => void;
|
||||||
roomId: string | null;
|
roomId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Lobby({ onCreateRoom, onJoinRoom, onStartAI, roomId }: LobbyProps) {
|
export function Lobby({ onCreateRoom, onJoinRoom, onStartAI }: LobbyProps) {
|
||||||
const [playerName, setPlayerName] = useState('');
|
const [playerName, setPlayerName] = useState('');
|
||||||
const [joinRoomId, setJoinRoomId] = useState('');
|
const [joinRoomId, setJoinRoomId] = useState('');
|
||||||
const [mode, setMode] = useState<'menu' | 'create' | 'join' | 'ai'>('menu');
|
const [mode, setMode] = useState<'menu' | 'create' | 'join' | 'ai'>('menu');
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ export function calculateTilePosition(
|
|||||||
const lastTile = side === 'right' ? board[board.length - 1] : board[0];
|
const lastTile = side === 'right' ? board[board.length - 1] : board[0];
|
||||||
let position: Position;
|
let position: Position;
|
||||||
let orientation: 'horizontal' | 'vertical' = 'horizontal';
|
let orientation: 'horizontal' | 'vertical' = 'horizontal';
|
||||||
let rotation = 0;
|
const rotation = 0;
|
||||||
|
|
||||||
if (side === 'right') {
|
if (side === 'right') {
|
||||||
const offset = lastTile.orientation === 'horizontal' ? tileWidth : tileHeight;
|
const offset = lastTile.orientation === 'horizontal' ? tileWidth : tileHeight;
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ io.on('connection', (socket) => {
|
|||||||
|
|
||||||
gameRooms.set(roomId, newGameState);
|
gameRooms.set(roomId, newGameState);
|
||||||
io.to(roomId).emit('game-state-updated', newGameState);
|
io.to(roomId).emit('game-state-updated', newGameState);
|
||||||
} catch (error) {
|
} catch {
|
||||||
socket.emit('invalid-move', 'Invalid move');
|
socket.emit('invalid-move', 'Invalid move');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"framer-motion": "^12.23.24",
|
"framer-motion": "^12.23.24",
|
||||||
"next": "16.0.1",
|
"next": "15.4.8",
|
||||||
"react": "19.2.0",
|
"react": "19.1.2",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.1.2",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.8.1",
|
||||||
"zustand": "^5.0.8"
|
"zustand": "^5.0.8"
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2017",
|
"target": "ES2017",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
@@ -11,7 +15,7 @@
|
|||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "preserve",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
@@ -19,7 +23,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
@@ -30,5 +36,7 @@
|
|||||||
".next/dev/types/**/*.ts",
|
".next/dev/types/**/*.ts",
|
||||||
"**/*.mts"
|
"**/*.mts"
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Referencia en una nueva incidencia
Block a user