diff options
author | pommicket <pommicket@gmail.com> | 2024-08-25 10:59:46 -0400 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2024-08-25 10:59:46 -0400 |
commit | becf8993cb1b7f560e17210b8b43b6c0e6c6e571 (patch) | |
tree | 6eb3de32f73f757f2124f8bfd9566c676fedeaff | |
parent | b20773247f97d0edb3475c8b56ec21e4237f7842 (diff) |
some fixes
-rw-r--r-- | game.js | 105 | ||||
-rw-r--r-- | server/src/main.rs | 42 |
2 files changed, 94 insertions, 53 deletions
@@ -102,6 +102,14 @@ window.addEventListener('load', function () { let x2 = randomSeed >> 16; return (x1 << 15 | x2) * (1 / (1 << 30)); } + function shuffle(l) { + for (let i = 0; i < l.length; i++) { + let j = Math.floor(random() * (i + 1)); + let temp = l[i]; + l[i] = l[j]; + l[j] = temp; + } + } const TOP_IN = 0; const TOP_OUT = 1; const RIGHT_IN = 2; @@ -334,51 +342,59 @@ window.addEventListener('load', function () { return clipPath.join(' '); } } - window.addEventListener('mouseup', function() { - if (draggingPiece) { - let anyConnected = false; - for (const piece of draggingPiece.connectedComponent) { - piece.setAnimate(true); - piece.element.style.zIndex = pieceZIndexCounter; - if (solved) break; - piece.needsServerUpdate = true; - const col = piece.col(); - const row = piece.row(); - const bbox = piece.boundingBox(); - for (const [nx, ny] of [[0, -1], [0, 1], [1, 0], [-1, 0]]) { - if (col + nx < 0 || col + nx >= puzzleWidth - || row + ny < 0 || row + ny >= puzzleHeight) { - continue; - } - let neighbour = pieces[piece.id + nx + ny * puzzleWidth]; - if (neighbour.connectedComponent === piece.connectedComponent) + function stopDraggingPiece() { + let anyConnected = false; + for (const piece of draggingPiece.connectedComponent) { + piece.element.style.zIndex = pieceZIndexCounter; + if (solved) break; + piece.needsServerUpdate = true; + const col = piece.col(); + const row = piece.row(); + const bbox = piece.boundingBox(); + for (const [nx, ny] of [[0, -1], [0, 1], [1, 0], [-1, 0]]) { + if (col + nx < 0 || col + nx >= puzzleWidth + || row + ny < 0 || row + ny >= puzzleHeight) { continue; - let neighbourBBox = neighbour.boundingBox(); - let keyPointMe = [nx === -1 ? bbox.left + nibSize : bbox.right - nibSize, - ny === -1 ? bbox.top + nibSize : bbox.bottom - nibSize]; - let keyPointNeighbour = [nx === 1 ? neighbourBBox.left + nibSize : neighbourBBox.right - nibSize, - ny === 1 ? neighbourBBox.top + nibSize : neighbourBBox.bottom - nibSize]; - let diff = [keyPointMe[0] - keyPointNeighbour[0], keyPointMe[1] - keyPointNeighbour[1]]; - let sqDist = diff[0] * diff[0] + diff[1] * diff[1]; - if (sqDist < connectRadius * connectRadius) { - anyConnected = true; - connectPieces(piece, neighbour, true); - if (multiplayer) { - socket.send(new Uint32Array([ACTION_CONNECT, piece.id, neighbour.id])); - } + } + let neighbour = pieces[piece.id + nx + ny * puzzleWidth]; + if (neighbour.connectedComponent === piece.connectedComponent) + continue; + let neighbourBBox = neighbour.boundingBox(); + let keyPointMe = [nx === -1 ? bbox.left + nibSize : bbox.right - nibSize, + ny === -1 ? bbox.top + nibSize : bbox.bottom - nibSize]; + let keyPointNeighbour = [nx === 1 ? neighbourBBox.left + nibSize : neighbourBBox.right - nibSize, + ny === 1 ? neighbourBBox.top + nibSize : neighbourBBox.bottom - nibSize]; + let diff = [keyPointMe[0] - keyPointNeighbour[0], keyPointMe[1] - keyPointNeighbour[1]]; + let sqDist = diff[0] * diff[0] + diff[1] * diff[1]; + if (sqDist < connectRadius * connectRadius) { + anyConnected = true; + connectPieces(piece, neighbour, true); + if (multiplayer) { + socket.send(new Uint32Array([ACTION_CONNECT, piece.id, neighbour.id])); } } } - draggingPiece.element.style.removeProperty('cursor'); - draggingPiece = null; - if (anyConnected) - connectAudio.play(); + } + draggingPiece.element.style.removeProperty('cursor'); + draggingPiece = null; + if (anyConnected) + connectAudio.play(); + setTimeout(() => { + for (const piece of draggingPiece.connectedComponent) + piece.setAnimate(true); + }, 1); + } + window.addEventListener('mouseup', function() { + if (draggingPiece) { + stopDraggingPiece(); } }); window.addEventListener('mousemove', function(e) { if (draggingPiece) { let dx = (e.clientX - draggingPieceLastPos.x) / playArea.clientWidth; let dy = (e.clientY - draggingPieceLastPos.y) / playArea.clientHeight; + let originalDx = dx; + let originalDy = dy; for (const piece of draggingPiece.connectedComponent) { // ensure pieces don't go past left edge dx = Math.max(dx, 0.0001 - piece.x); @@ -396,6 +412,10 @@ window.addEventListener('load', function () { } draggingPieceLastPos.x = e.clientX; draggingPieceLastPos.y = e.clientY; + if (dx !== originalDx || dy !== originalDy) { + // stop dragging piece if it was dragged past edge + stopDraggingPiece(); + } } }); function loadImage() { @@ -439,7 +459,7 @@ window.addEventListener('load', function () { pieceWidth = pieceHeight * (puzzleHeight / puzzleWidth) * (image.width / image.height); } // ensure full puzzle doesn't take up too much screen space - while (pieceWidth * puzzleWidth * pieceHeight * puzzleHeight > Math.max(1000, 0.5 * playArea.clientWidth * playArea.clientHeight)) { + while (pieceWidth * puzzleWidth * pieceHeight * puzzleHeight > Math.max(1000, 0.4 * playArea.clientWidth * playArea.clientHeight)) { pieceWidth *= 0.9; pieceHeight *= 0.9; } @@ -486,6 +506,19 @@ window.addEventListener('load', function () { getById('host-multiplayer').style.display = 'inline-block'; setRandomSeed(puzzleSeed); createPieces(); + const piecePositions = []; + for (let y = 0; y < puzzleHeight; y++) { + for (let x = 0; x < puzzleWidth; x++) { + piecePositions.push([(x + random() * 0.3) / (puzzleWidth + 1), + (y + random() * 0.3) / (puzzleHeight + 1)]); + } + } + shuffle(piecePositions); + for (const piece of pieces) { + piece.x = piecePositions[piece.id][0]; + piece.y = piecePositions[piece.id][1]; + piece.updatePosition(); + } // a bit janky, but it stops the pieces from animating to their starting positions setTimeout(() => { for (const piece of pieces) { diff --git a/server/src/main.rs b/server/src/main.rs index a0a1d39..5e62213 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -83,7 +83,7 @@ impl Server { self.database .execute( &self.set_puzzle_data, - &[&width, &height, &url, &connectivity, &positions, &id, &seed], + &[&width, &height, &url, &connectivity, &positions, &seed, &id], ) .await?; Ok(()) @@ -174,7 +174,7 @@ enum Error { UTF8(std::str::Utf8Error), BadPuzzleID, BadPieceID, - BadSyntax, + BadSyntax(&'static str), ImageURLTooLong, TooManyPieces, TooManyPlayers, @@ -186,7 +186,7 @@ impl std::fmt::Display for Error { match self { Error::BadPieceID => write!(f, "bad piece ID"), Error::BadPuzzleID => write!(f, "bad puzzle ID"), - Error::BadSyntax => write!(f, "bad syntax"), + Error::BadSyntax(s) => write!(f, "bad syntax: {s}"), Error::ImageURLTooLong => write!(f, "image URL too long"), Error::TooManyPieces => write!(f, "too many pieces"), Error::NotJoined => write!(f, "haven't joined a puzzle"), @@ -267,29 +267,32 @@ async fn handle_websocket( let mut parts = dimensions.split(' '); let width: u8 = parts .next() - .ok_or(Error::BadSyntax)? + .ok_or(Error::BadSyntax("no width"))? .parse() - .map_err(|_| Error::BadSyntax)?; + .map_err(|_| Error::BadSyntax("width not integer"))?; let height: u8 = parts .next() - .ok_or(Error::BadSyntax)? + .ok_or(Error::BadSyntax("no height"))? .parse() - .map_err(|_| Error::BadSyntax)?; + .map_err(|_| Error::BadSyntax("height not integer"))?; if width < 3 || height < 3 { - return Err(Error::BadSyntax); + return Err(Error::BadSyntax("dimensions too small")); } if usize::from(width) * usize::from(height) > MAX_PIECES { return Err(Error::TooManyPieces); } - let url: String = parts.next().ok_or(Error::BadSyntax)?.replace(';', " "); + let url: String = parts + .next() + .ok_or(Error::BadSyntax("no URL"))? + .replace(';', " "); if url.len() > 2048 { return Err(Error::ImageURLTooLong); } let seed = parts .next() - .ok_or(Error::BadSyntax)? + .ok_or(Error::BadSyntax("no seed"))? .parse() - .map_err(|_| Error::BadSyntax)?; + .map_err(|_| Error::BadSyntax("seed not integer"))?; let piece_positions = vec![0.0f32; 2 * (width as usize) * (height as usize)]; let mut connectivity_data: Vec<u16> = Vec::with_capacity(usize::from(width) * usize::from(height)); @@ -319,7 +322,10 @@ async fn handle_websocket( ws.send(Message::Text(format!("id: {}", std::str::from_utf8(&id)?))) .await?; } else if let Some(id) = text.strip_prefix("join ") { - let id = id.as_bytes().try_into().map_err(|_| Error::BadSyntax)?; + let id = id + .as_bytes() + .try_into() + .map_err(|_| Error::BadSyntax("bad join ID"))?; let mut player_counts = server.player_counts.lock().await; let entry = player_counts.entry(id).or_default(); if *entry >= MAX_PLAYERS { @@ -367,14 +373,16 @@ async fn handle_websocket( } } else if let Message::Binary(data) = &message { if data.len() % 4 != 0 { - return Err(Error::BadSyntax); + return Err(Error::BadSyntax("binary message not multiple of 4 bytes")); } let puzzle_id = puzzle_id.ok_or(Error::NotJoined)?; let mut reader_data = std::io::Cursor::new(data); let reader = &mut reader_data; fn read<const N: usize>(reader: &mut std::io::Cursor<&Vec<u8>>) -> Result<[u8; N]> { let mut data = [0; N]; - reader.read_exact(&mut data).map_err(|_| Error::BadSyntax)?; + reader + .read_exact(&mut data) + .map_err(|_| Error::BadSyntax("unexpected EOF in action sequence"))?; Ok(data) } fn read_u32(reader: &mut std::io::Cursor<&Vec<u8>>) -> Result<u32> { @@ -384,7 +392,7 @@ async fn handle_websocket( Ok(f32::from_le_bytes(read(reader)?)) } let message_id = read_u32(reader)?; - while !reader.get_ref().is_empty() { + while reader.position() < reader.get_ref().len() as u64 { let action = read_u32(reader)?; if action == ACTION_MOVE { let piece: usize = read_u32(reader)? as _; @@ -392,7 +400,7 @@ async fn handle_websocket( let y: f32 = read_f32(reader)?; for coord in [x, y] { if !coord.is_finite() || coord < 0.0 || coord > 2.0 { - return Err(Error::BadSyntax); + return Err(Error::BadSyntax("piece position out of bounds")); } } server.move_piece(puzzle_id, piece, x, y).await?; @@ -401,7 +409,7 @@ async fn handle_websocket( let piece2: usize = read_u32(reader)? as _; server.connect_pieces(puzzle_id, piece1, piece2).await?; } else { - return Err(Error::BadSyntax); + return Err(Error::BadSyntax("bad action")); } } ws.send(Message::Text(format!("ack {message_id}"))).await?; |