Update game mechanics and improve pawn movement logic; refactor connection URI and enhance server list handling

This commit is contained in:
2026-01-30 16:23:19 +05:30
parent ed0b6cf589
commit f7f8798f43
9 changed files with 196 additions and 33 deletions
+97 -10
View File
@@ -6,6 +6,7 @@ import positionMapCoords from '../positions';
import pawnImages from '../../../constants/pawnImages';
import canPawnMove from './canPawnMove';
import getPositionAfterMove from './getPositionAfterMove';
import { pawnsAt } from './pawnHelpers';
const Map = ({ pawns, nowMoving, rolledNumber }) => {
const player = useContext(PlayerDataContext);
@@ -14,15 +15,26 @@ const Map = ({ pawns, nowMoving, rolledNumber }) => {
const [hintPawn, setHintPawn] = useState();
const paintPawn = (context, pawn) => {
const { x, y } = positionMapCoords[pawn.position];
const paintPawn = (context, pawn, x, y, pawnWidth = 28, onDraw) => {
const pawnHeight = Math.round((pawnWidth * 30) / 35);
const touchableArea = new Path2D();
touchableArea.arc(x, y, 12, 0, 2 * Math.PI);
const touchRadius = Math.max(6, Math.round(pawnWidth / 2));
touchableArea.arc(x, y, touchRadius, 0, 2 * Math.PI);
const image = new Image();
image.src = pawnImages[pawn.color];
image.onload = function () {
context.drawImage(image, x - 17, y - 15, 35, 30);
const imgSrc = pawnImages[pawn.color] || pawnImages['red'];
image.src = imgSrc;
const drawAndCallback = () => {
context.drawImage(image, x - Math.round(pawnWidth / 2), y - Math.round(pawnHeight / 2), pawnWidth, pawnHeight);
if (typeof onDraw === 'function') onDraw();
};
if (image.complete) {
drawAndCallback();
} else {
image.onload = drawAndCallback;
}
return touchableArea;
};
@@ -56,8 +68,6 @@ const Map = ({ pawns, nowMoving, rolledNumber }) => {
canPawnMove(pawn, rolledNumber)
) {
const pawnPosition = getPositionAfterMove(pawn, rolledNumber);
console.log('previous position:', pawn.position);
console.log('Hovered pawn can move to position:', pawnPosition);
if (pawnPosition) {
canvas.style.cursor = 'pointer';
if (hintPawn && hintPawn.id === pawn._id) return;
@@ -77,11 +87,88 @@ const Map = ({ pawns, nowMoving, rolledNumber }) => {
image.src = mapImage;
image.onload = function () {
ctx.drawImage(image, 0, 0);
// draw pawns grouped by position with 2-row layout
const drawn = new Set();
pawns.forEach((pawn, index) => {
pawns[index].touchableArea = paintPawn(ctx, pawn);
if (drawn.has(pawn._id)) return;
const group = pawnsAt(pawns, pawn.position);
const total = group.length;
const center = positionMapCoords[pawn.position];
if (!center) return;
const slotOffsetX = 14; // horizontal spacing
const slotOffsetY = 2; // minimal vertical spacing between rows
// helper to draw a single representative pawn for a color group
// and show a small badge with the count when there are multiple pawns
const drawColorGroup = (pawnGroup, x, y, baseWidth) => {
if (!pawnGroup || pawnGroup.length === 0) return null;
const rep = pawnGroup[0];
// draw pawn and then badge after image renders so badge is on top
const touch = paintPawn(ctx, rep, x, y, baseWidth, () => {
if (pawnGroup.length > 1) {
const badgeRadius = 8; // smaller badge
const badgeX = x + Math.round(baseWidth / 2) - 4; // slightly closer to pawn edge
const badgeY = y - Math.round(baseWidth / 2) + 4;
ctx.beginPath();
ctx.fillStyle = 'rgba(0,0,0,0.85)';
ctx.arc(badgeX, badgeY, badgeRadius, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'white';
ctx.font = '10px Arial'; // smaller text
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(String(pawnGroup.length), badgeX, badgeY);
}
});
const foundIndex = pawns.findIndex(pp => pp._id === rep._id);
if (foundIndex !== -1) pawns[foundIndex].touchableArea = touch;
return touch;
};
// group pawns by color so same-color pawns stack vertically (tight)
const colorGroups = {};
group.forEach(g => {
if (!g) return;
if (!colorGroups[g.color]) colorGroups[g.color] = [];
colorGroups[g.color].push(g);
});
const colors = Object.keys(colorGroups);
const nColors = colors.length;
const colorSpacing = slotOffsetX; // horizontal spacing between different colors
colors.forEach((color, ci) => {
const pawnsOfColor = colorGroups[color];
const x = center.x + (ci - (nColors - 1) / 2) * colorSpacing;
const baseWidth = pawnsOfColor.length > 1 ? 20 : total === 1 ? 28 : 24;
drawColorGroup(pawnsOfColor, x, center.y, baseWidth);
pawnsOfColor.forEach(p => drawn.add(p._id));
});
// badge for >6 pawns
if (total > 6) {
const badgeX = center.x + slotOffsetX + 12;
const badgeY = center.y - slotOffsetY - 12;
ctx.beginPath();
ctx.fillStyle = 'rgba(0,0,0,0.75)';
ctx.arc(badgeX, badgeY, 12, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'white';
ctx.font = '11px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(String(total), badgeX, badgeY);
}
});
if (hintPawn) {
paintPawn(ctx, hintPawn);
// draw hint as a grey circle at target position
const pos = positionMapCoords[hintPawn.position];
if (pos) {
ctx.beginPath();
ctx.fillStyle = 'rgba(128,128,128,0.6)';
ctx.arc(pos.x, pos.y, 14, 0, Math.PI * 2);
ctx.fill();
}
}
};
};
+1 -1
View File
@@ -1,6 +1,6 @@
const canPawnMove = (pawn, rolledNumber) => {
// If is in base
if ((rolledNumber === 1 || rolledNumber === 6) && pawn.position === pawn.basePos) {
if ((rolledNumber === 6) && pawn.position === pawn.basePos) {
return true;
// Other situations: pawn is on map or pawn is in end positions
} else if (pawn.position !== pawn.basePos) {
@@ -0,0 +1,10 @@
export const pawnsAt = (pawns, position) => {
return Array.isArray(pawns) ? pawns.filter(p => p && p.position === position) : [];
};
export const countPawnsAt = (pawns, position) => pawnsAt(pawns, position).length;
export const opponentsAt = (pawns, position, myColor) =>
pawnsAt(pawns, position).filter(p => p.color !== myColor);
export default { pawnsAt, countPawnsAt, opponentsAt };
@@ -2,6 +2,7 @@ import lock from '../../../../images/login-page/lock.png';
import styles from './ServersTable.module.css';
const ServerListTable = ({ rooms, handleJoinClick, handleDeleteClick }) => {
const safeRooms = Array.isArray(rooms) ? rooms : [];
return (
<table className={styles.rooms}>
<thead>
@@ -14,12 +15,13 @@ const ServerListTable = ({ rooms, handleJoinClick, handleDeleteClick }) => {
</tr>
</thead>
<tbody>
{rooms.map((room, index) => {
{safeRooms.map((room, index) => {
if (!room) return null;
return room.started ? null : (
<tr key={index}>
<td>{room.private ? <img src={lock} alt='private' /> : null}</td>
<td className={styles.roomName}>{room.name}</td>
<td>{`${room.players.length}/4`}</td>
<td>{`${(room.players && room.players.length) || 0}/4`}</td>
<td>{room.isStarted ? 'started' : 'waiting'}</td>
<td className={styles.lastColumn}>
<button onClick={() => handleJoinClick(room)}>Join</button>