diff --git a/web/src/App.js b/web/src/App.js deleted file mode 100644 index 9c280bd..0000000 --- a/web/src/App.js +++ /dev/null @@ -1,440 +0,0 @@ -import React, { useRef, useEffect, useState, useCallback } from "react"; -import { library } from "@fortawesome/fontawesome-svg-core"; -import { - faDollarSign, - faMoneyBill, - faSackDollar, - faMoneyBill1, - faLandmark, - faWallet, - faCoins, - faReceipt, - faVault, - faPiggyBank, - faMoneyBillWave, - faMoneyBill1Wave, - faMoneyBillTransfer, -} from "@fortawesome/free-solid-svg-icons"; -// import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import "./App.css"; -import JsBarcode from "jsbarcode"; // Import jsbarcode - -// Add the icons to the library -library.add( - faMoneyBill, - faSackDollar, - faMoneyBill1, - faLandmark, - faWallet, - faCoins, - faReceipt, - faVault, - faPiggyBank, - faMoneyBillWave, - faMoneyBill1Wave, - faMoneyBillTransfer -); - -function App() { - const prizeCanvasRef = useRef(null); - const scratchCanvasRef = useRef(null); - const barcodeCanvasRef = useRef(null); // Barcode canvas reference - const [isDrawing, setIsDrawing] = useState(false); - const [canvasSize, setCanvasSize] = useState({ width: 600, height: 900 }); // Fixed size for 5x5 grid - const [hasWon, setHasWon] = useState(false); - const [shapes, setShapes] = useState([]); // Track the shapes in the grid - const rows = 5; - const cols = 5; - - const handleResize = useCallback(() => { - setCanvasSize({ width: 600, height: 900 }); // Fixed size for 5x5 grid - }, []); - - const generateShapes = useCallback(() => { - const newShapes = []; - - for (let i = 0; i < rows; i++) { - const row = []; - for (let j = 0; j < cols; j++) { - const shapeType = "shape"; - row.push({ type: shapeType }); - } - newShapes.push(row); - } - setShapes(newShapes); - }, []); - - const generateBarcode = useCallback(() => { - const generateRandomNumber = () => { - let randomBarcode = ""; - while (randomBarcode.length < 23) { - randomBarcode += Math.floor(Math.random() * 10); // Append a random digit (0-9) - } - return randomBarcode; - }; - const randomBarcode = generateRandomNumber(); - console.log(randomBarcode); - const barcodeCanvas = barcodeCanvasRef.current; - JsBarcode(barcodeCanvas, randomBarcode, { - format: "CODE128", - width: 3, - height: 40, - background: "#555555", - lineColor: "#ffffff" - }); - }, []); - - const drawPrizeLayer = useCallback(() => { - const icons = [ - faMoneyBill, - faSackDollar, - faMoneyBill1, - faLandmark, - faWallet, - faCoins, - faReceipt, - faVault, - faPiggyBank, - faMoneyBillWave, - faMoneyBill1Wave, - faMoneyBillTransfer, - ]; - const amounts = [1, 2, 5, 10, 20, 50, 100]; - const prizeCanvas = prizeCanvasRef.current; - const ctx = prizeCanvas.getContext("2d"); - const cellWidth = prizeCanvas.width / cols; - const cellHeight = (prizeCanvas.height - 100) / rows; - - // Clear the prize layer before drawing shapes - ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); - - ctx.fillStyle = "#555"; - ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); - - shapes.forEach((row, i) => { - row.forEach((shape, j) => { - const x = j * cellWidth; - const y = i * cellHeight; - - // Draw border around each cell with rounded corners - const borderPadding = 0.05 * cellWidth; - const borderRadius = 10; // Radius for rounded corners - ctx.strokeStyle = "white"; // Inverted border color - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.moveTo(x + borderPadding + borderRadius, y + borderPadding); - ctx.lineTo( - x + cellWidth - borderPadding - borderRadius, - y + borderPadding - ); - ctx.quadraticCurveTo( - x + cellWidth - borderPadding, - y + borderPadding, - x + cellWidth - borderPadding, - y + borderPadding + borderRadius - ); - ctx.lineTo( - x + cellWidth - borderPadding, - y + cellHeight - borderPadding - borderRadius - ); - ctx.quadraticCurveTo( - x + cellWidth - borderPadding, - y + cellHeight - borderPadding, - x + cellWidth - borderPadding - borderRadius, - y + cellHeight - borderPadding - ); - ctx.lineTo( - x + borderPadding + borderRadius, - y + cellHeight - borderPadding - ); - ctx.quadraticCurveTo( - x + borderPadding, - y + cellHeight - borderPadding, - x + borderPadding, - y + cellHeight - borderPadding - borderRadius - ); - ctx.lineTo(x + borderPadding, y + borderPadding + borderRadius); - ctx.quadraticCurveTo( - x + borderPadding, - y + borderPadding, - x + borderPadding + borderRadius, - y + borderPadding - ); - ctx.closePath(); - ctx.stroke(); - - // Choose a random icon from the list - const randomIcon = - icons[Math.floor(Math.random() * icons.length)]; - const [width, height, , , svgPathData] = randomIcon.icon; - - const svgString = ` - - - `; - const img = new Image(); - img.src = `data:image/svg+xml;base64,${btoa(svgString)}`; - - img.onload = () => { - // Calculate the size of the icon to occupy a square dimension at the top of the cell - const iconSize = Math.min(cellWidth, cellHeight) * 0.5; - const offsetX = (cellWidth - iconSize) / 2; - const offsetY = cellHeight * 0.2; - - ctx.drawImage( - img, - x + offsetX, - y + offsetY, - iconSize, - iconSize - ); - - // Choose a random amount from the list - const amount = - amounts[Math.floor(Math.random() * amounts.length)]; - - // Draw the amount just below the icon - ctx.font = `bold ${ - cellHeight * 0.18 - }px 'Roboto', sans-serif`; - ctx.fillStyle = "white"; // Inverted text color - ctx.textAlign = "center"; - ctx.fillText( - `$${amount}`, - x + cellWidth / 2, - y + offsetY + iconSize + cellHeight * 0.2 - ); - }; - }); - }); - }, [shapes]); - - const drawScratchLayer = useCallback(() => { - const scratchCanvas = scratchCanvasRef.current; - const ctx = scratchCanvas.getContext("2d"); - - // Create a canvas for the sparkle pattern - const sparkleCanvas = document.createElement("canvas"); - const sparkleCtx = sparkleCanvas.getContext("2d"); - sparkleCanvas.width = 100; - sparkleCanvas.height = 100; - - // Draw the purple base - sparkleCtx.fillStyle = "#00963C"; - sparkleCtx.fillRect(0, 0, sparkleCanvas.width, sparkleCanvas.height); - - // Draw random sparkles - for (let i = 0; i < 75; i++) { - sparkleCtx.fillStyle = "rgba(255, 255, 255, " + Math.random() + ")"; - sparkleCtx.beginPath(); - sparkleCtx.arc( - Math.random() * 100, - Math.random() * 100, - Math.random() * 2, - 0, - Math.PI * 2 - ); - sparkleCtx.fill(); - } - - // Create a pattern from the sparkle canvas - const sparklePattern = ctx.createPattern(sparkleCanvas, "repeat"); - - // Fill the scratch area with the sparkle pattern - ctx.globalCompositeOperation = "source-over"; - ctx.fillStyle = sparklePattern; - ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); - - // Draw a thicker black border around each cell - const cellWidth = scratchCanvas.width / cols; - const cellHeight = (scratchCanvas.height - 100) / rows; - const borderWidth = cellWidth * 0.04; - - for (let i = 0; i < rows; i++) { - for (let j = 0; j < cols; j++) { - const x = j * cellWidth; - const y = i * cellHeight; - - // Draw black border around each cell - ctx.strokeStyle = "black"; - ctx.lineWidth = borderWidth; - ctx.strokeRect(x, y, cellWidth, cellHeight); - - // Get Font Awesome SVG data - const icon = faDollarSign.icon; - const [width, height, , , svgPathData] = icon; - - // Create an SVG string with the gold gradient - const svgString = ` - - - - - - - - - `; - const img = new Image(); - img.src = `data:image/svg+xml;base64,${btoa(svgString)}`; - - img.onload = () => { - // Calculate the size of the icon to occupy 80% of the cell - const iconWidth = cellWidth * 0.8; - const iconHeight = cellHeight * 0.8; - const offsetX = (cellWidth - iconWidth) / 2; - const offsetY = (cellHeight - iconHeight) / 2; - - // Draw the SVG symbols in a grid - ctx.drawImage( - img, - x + offsetX, - y + offsetY, - iconWidth, - iconHeight - ); - }; - } - } - }, []); - - useEffect(() => { - handleResize(); // Set the fixed size on component mount - generateShapes(); - generateBarcode(); - }, [generateShapes, handleResize, generateBarcode]); - - useEffect(() => { - drawPrizeLayer(); - drawScratchLayer(); - }, [shapes, canvasSize, drawPrizeLayer, drawScratchLayer]); - - const handleMouseDown = () => { - setIsDrawing(true); - }; - - const handleMouseUp = () => { - setIsDrawing(false); - }; - - const handleMouseMove = (event) => { - if (event.buttons !== 1) { - setIsDrawing(false); - return; - } - - setIsDrawing(true); // Ensure drawing continues if the mouse is pressed - - if (!isDrawing || hasWon) return; - - const canvas = scratchCanvasRef.current; - const ctx = canvas.getContext("2d"); - - const rect = canvas.getBoundingClientRect(); - const x = event.clientX - rect.left; - const y = event.clientY - rect.top; - - const eraserSize = 50; // Diameter of the eraser circle - - ctx.globalCompositeOperation = "destination-out"; - ctx.beginPath(); - ctx.arc(x, y, eraserSize / 2, 0, Math.PI * 2, true); - ctx.fill(); - - // Calculate erased area - const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - let erasedPixels = 0; - - for (let i = 0; i < imageData.data.length; i += 4) { - if (imageData.data[i + 3] === 0) { - erasedPixels++; - } - } - - const totalPixels = canvas.width * canvas.height; - const erasedPercentage = (erasedPixels / totalPixels) * 100; - - if (erasedPercentage > 75) { - setHasWon(true); - animateScratchLayerErase(); - } - }; - - const handleMouseLeave = () => { - setIsDrawing(false); - }; - - useEffect(() => { - window.addEventListener("mouseup", handleMouseUp); - return () => { - window.removeEventListener("mouseup", handleMouseUp); - }; - }, []); - - const resetGame = () => { - setHasWon(false); - generateShapes(); - drawScratchLayer(); // Ensure the cover layer is redrawn - }; - - const animateScratchLayerErase = () => { - const canvas = scratchCanvasRef.current; - const ctx = canvas.getContext("2d"); - const centerX = canvas.width / 2; - const centerY = canvas.height / 2; - const maxRadius = Math.sqrt(centerY * centerY + centerY * centerY); - let currentRadius = 0; - const speed = 5; - - ctx.globalCompositeOperation = "destination-out"; - const eraseCircle = () => { - ctx.beginPath(); - ctx.arc(centerX, centerY, currentRadius, 0, Math.PI * 2); - ctx.fill(); - - currentRadius += speed; - if (currentRadius < maxRadius) { - requestAnimationFrame(eraseCircle); - } else { - setHasWon(true); - } - }; - - eraseCircle(); - }; - - return ( -
- {hasWon &&
Congratulations, You Win!
} -
- - - -
- {hasWon && ( - - )} -
- ); -} - -export default App; diff --git a/web/src/components/App.tsx b/web/src/components/App.tsx index b42dd8b..3c42a7e 100644 --- a/web/src/components/App.tsx +++ b/web/src/components/App.tsx @@ -1,4 +1,5 @@ import React, { useRef, useEffect, useState, useCallback } from "react"; +import { isEnvBrowser } from "../utils/misc"; import { icon, library } from "@fortawesome/fontawesome-svg-core"; import { faDollarSign, @@ -16,9 +17,8 @@ import { faMoneyBillTransfer, } from "@fortawesome/free-solid-svg-icons"; import "./App.css"; -import JsBarcode from "jsbarcode"; // Import jsbarcode +import JsBarcode from "jsbarcode"; -// Add the icons to the library library.add( faMoneyBill, faSackDollar, @@ -47,26 +47,20 @@ debugData([ ]); debugData([ { - action: "gridSizeX", - data: 4, - }, -]); -debugData([ - { - action: "gridSizeY", - data: 4, + action: "cardSetup", + data: { gridSizeX: 5, gridSizeY: 5, cardBgColor: "#ff00ff" }, }, ]); debugData([ { action: "prizeData", data: [ - { icon: "money-bill", amount: 20 }, - { icon: "money-bill", amount: 21 }, - { icon: "landmark", amount: 22 }, - { icon: "landmark", amount: 23 }, - { icon: "wallet", amount: 24 }, - { icon: "wallet", amount: 25 }, + { icon: "money-bill", amount: 20, color: "gold" }, + { icon: "money-bill", amount: 20, color: "gold" }, + { icon: "landmark", amount: 50 }, + { icon: "landmark", amount: 50 }, + { icon: "wallet", amount: 10 }, + { icon: "wallet", amount: 10 }, { icon: "sack-dollar", amount: 26 }, { icon: "sack-dollar", amount: 27 }, { icon: "money-bill-1", amount: 28 }, @@ -84,6 +78,7 @@ debugData([ { icon: "money-bill-1-wave", amount: 40 }, { icon: "money-bill-1-wave", amount: 41 }, { icon: "money-bill-transfer", amount: 42 }, + { icon: "money-bill-transfer", amount: 43 }, { icon: "money-bill-transfer", amount: 43 } ], }, @@ -97,46 +92,36 @@ interface CardData { } const App: React.FC = () => { - const closeGame = () => { - fetchNui("scratcherComplete"); - }; const prizeCanvasRef = useRef(null); const scratchCanvasRef = useRef(null); - const barcodeCanvasRef = useRef(null); // Barcode canvas reference + const barcodeCanvasRef = useRef(null); const [isDrawing, setIsDrawing] = useState(false); - const [canvasSize, setCanvasSize] = useState({ width: 600, height: 900 }); // Fixed size for 5x5 grid + const [canvasSize, setCanvasSize] = useState({ width: 600, height: 900 }); const [hasWon, setHasWon] = useState(false); - const [prizeData, setPrizeData] = useState([]); // Ensure it's initialized as an array - const [gridSizeX, setGridSizeX] = useState(5); // Hold grid size X - const [gridSizeY, setGridSizeY] = useState(5); // Hold grid size Y - const [cardBgColor, setBgColor] = useState("#00963C"); // Hold scratcher color + const [prizeData, setPrizeData] = useState([]); + const [gridSizeX, setGridSizeX] = useState(5); + const [gridSizeY, setGridSizeY] = useState(5); + const [cardBgColor, setBgColor] = useState("#00963C"); const shuffleArray = (array: any[]) => { let currentIndex = array.length, randomIndex; - // While there remain elements to shuffle. while (currentIndex !== 0) { - // Pick a remaining element. randomIndex = Math.floor(Math.random() * currentIndex); currentIndex--; - // And swap it with the current element. [array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]]; } return array; }; - const handleResize = useCallback(() => { - setCanvasSize({ width: 600, height: 900 }); // Fixed size for 5x5 grid - }, []); - const generateBarcode = useCallback(() => { const generateRandomNumber = () => { let randomBarcode = ""; while (randomBarcode.length < 23) { - randomBarcode += Math.floor(Math.random() * 10); // Append a random digit (0-9) + randomBarcode += Math.floor(Math.random() * 10); } return randomBarcode; }; @@ -159,7 +144,6 @@ const App: React.FC = () => { const cellWidth = prizeCanvas.width / gridSizeY; const cellHeight = (prizeCanvas.height - 100) / gridSizeX; - // Clear the prize layer before drawing shapes ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.fillStyle = "#555"; @@ -171,10 +155,9 @@ const App: React.FC = () => { const x = j * cellWidth; const y = i * cellHeight; - // Draw border around each cell with rounded corners const borderPadding = 0.05 * cellWidth; - const borderRadius = 10; // Radius for rounded corners - ctx.strokeStyle = "white"; // Inverted border color + const borderRadius = 10; + ctx.strokeStyle = "white"; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(x + borderPadding + borderRadius, y + borderPadding); @@ -217,7 +200,6 @@ const App: React.FC = () => { ); ctx.closePath(); ctx.stroke(); - const iconData = icon({ prefix: 'fas', iconName: prizeData[prizeIndex]?.icon}); const iconColor = prizeData[prizeIndex]?.color || 'white'; const [width, height, , , svgPathData] = iconData.icon; @@ -229,11 +211,9 @@ const App: React.FC = () => { const img = new Image(); img.src = `data:image/svg+xml;base64,${btoa(svgString)}`; - // Choose a random amount from the list const amount = prizeData[prizeIndex]?.amount || 1; prizeIndex++; img.onload = () => { - // Calculate the size of the icon to occupy a square dimension at the top of the cell const iconSize = Math.min(cellWidth, cellHeight) * 0.5; const offsetX = (cellWidth - iconSize) / 2; const offsetY = cellHeight * 0.2; @@ -246,12 +226,10 @@ const App: React.FC = () => { iconSize ); - - // Draw the amount just below the icon ctx.font = `bold ${ cellHeight * 0.18 }px 'Roboto', sans-serif`; - ctx.fillStyle = "white"; // Inverted text color + ctx.fillStyle = "white"; ctx.textAlign = "center"; ctx.fillText( `$${amount}`, @@ -272,18 +250,15 @@ const App: React.FC = () => { }); if (!ctx) return; - // Create a canvas for the sparkle pattern const sparkleCanvas = document.createElement("canvas"); const sparkleCtx = sparkleCanvas.getContext("2d"); sparkleCanvas.width = 100; sparkleCanvas.height = 100; - // Draw the purple base if (sparkleCtx) { sparkleCtx.fillStyle = cardBgColor; sparkleCtx.fillRect(0, 0, sparkleCanvas.width, sparkleCanvas.height); - // Draw random sparkles for (let i = 0; i < 75; i++) { sparkleCtx.fillStyle = "rgba(255, 255, 255, " + Math.random() + ")"; sparkleCtx.beginPath(); @@ -298,16 +273,13 @@ const App: React.FC = () => { } }; - // Create a pattern from the sparkle canvas const sparklePattern = ctx.createPattern(sparkleCanvas, "repeat"); if (!sparklePattern) return; - // Fill the scratch area with the sparkle pattern ctx.globalCompositeOperation = "source-over"; ctx.fillStyle = sparklePattern; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); - // Draw a thicker black border around each cell const cellWidth = scratchCanvas.width / gridSizeY; const cellHeight = (scratchCanvas.height - 100) / gridSizeX; const borderWidth = cellWidth * 0.04; @@ -317,16 +289,13 @@ const App: React.FC = () => { const x = j * cellWidth; const y = i * cellHeight; - // Draw black border around each cell ctx.strokeStyle = "black"; ctx.lineWidth = borderWidth; ctx.strokeRect(x, y, cellWidth, cellHeight); - // Get Font Awesome SVG data const icon = faDollarSign.icon; const [width, height, , , svgPathData] = icon; - // Create an SVG string with the gold gradient const svgString = ` @@ -341,13 +310,11 @@ const App: React.FC = () => { img.src = `data:image/svg+xml;base64,${btoa(svgString)}`; img.onload = () => { - // Calculate the size of the icon to occupy 80% of the cell const iconWidth = cellWidth * 0.8; const iconHeight = cellHeight * 0.8; const offsetX = (cellWidth - iconWidth) / 2; const offsetY = (cellHeight - iconHeight) / 2; - // Draw the SVG symbols in a grid ctx.drawImage( img, x + offsetX, @@ -360,16 +327,6 @@ const App: React.FC = () => { } }, [gridSizeX, gridSizeY, cardBgColor]); - useEffect(() => { - handleResize(); // Set the fixed size on component mount - generateBarcode(); - }, [handleResize, generateBarcode]); - - // useEffect(() => { - // drawPrizeLayer(); - // drawScratchLayer(); - // }, [canvasSize, drawPrizeLayer, drawScratchLayer]); - const handleMouseDown = () => { setIsDrawing(true); }; @@ -384,7 +341,7 @@ const App: React.FC = () => { return; } - setIsDrawing(true); // Ensure drawing continues if the mouse is pressed + setIsDrawing(true); if (!isDrawing || hasWon) return; @@ -398,14 +355,13 @@ const App: React.FC = () => { const x = event.clientX - rect.left; const y = event.clientY - rect.top; - const eraserSize = 100; // Diameter of the eraser circle + const eraserSize = 100; ctx.globalCompositeOperation = "destination-out"; ctx.beginPath(); ctx.arc(x, y, eraserSize / 2, 0, Math.PI * 2, true); ctx.fill(); - // Calculate erased area const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); let erasedPixels = 0; @@ -435,13 +391,6 @@ const App: React.FC = () => { }; }, []); - const resetGame = () => { - setHasWon(false); - generateBarcode(); - drawPrizeLayer(); - drawScratchLayer(); // Ensure the cover layer is redrawn - }; - useNuiEvent("cardSetup", (cardData: CardData) => { if (cardData && typeof cardData.gridSizeX === 'number' && typeof cardData.gridSizeY === 'number' && typeof cardData.cardBgColor === 'string') { setGridSizeX(cardData.gridSizeX); @@ -460,6 +409,13 @@ const App: React.FC = () => { } }); + const resetGame = () => { + setHasWon(false); + generateBarcode(); + drawPrizeLayer(); + drawScratchLayer(); + }; + useEffect(() => { if (prizeData.length > 0) { resetGame(); @@ -495,6 +451,14 @@ const App: React.FC = () => { eraseCircle(); }; + const closeGame = () => { + if (isEnvBrowser()) { + resetGame(); + } else { + fetchNui("scratcherComplete"); + } + }; + return (