Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/common/blockchainWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ async function getTokenSymbol() {
const rouletteContractEvents = new ethers.Contract(
ROULETTE_CONTRACT_ADDRESS,
[
'event ExecutedWager(address indexed, uint256)',
'event ExecutedWager(address indexed, uint256, uint256, uint256)',
'event BetPlaced(address indexed, string, uint256)',
'event BetCleared(address indexed)',
],
provider
);
Expand Down
29 changes: 29 additions & 0 deletions src/components/roulette/ClearBetsButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const CLASS_NAME = "ClearBetsButton-component";

export function ClearBetsButton(props) {
const hasBets = props.pendingBets && props.pendingBets.length > 0;
const isDisabled = props.wheelIsSpinning || !hasBets;

return (
<div className={CLASS_NAME}>
<button
className="clear-bets-button"
onClick={props.onClick}
disabled={isDisabled}
style={{
backgroundColor: isDisabled ? "#cccccc" : "#ff4444",
color: "white",
border: "none",
borderRadius: "8px",
padding: "12px 24px",
fontSize: "16px",
fontWeight: "bold",
cursor: isDisabled ? "not-allowed" : "pointer",
opacity: isDisabled ? 0.6 : 1,
}}
>
Clear All Bets
</button>
</div>
);
}
27 changes: 17 additions & 10 deletions src/components/roulette/MostRecentSpinResults.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,24 @@ const CLASS_NAME = "MostRecentSpinResults-component";
export function MostRecentSpinResults(props) {
const [spinResults, setSpinResults] = useState([]);

rouletteContractEvents.on('ExecutedWager', (playerAddress, wheelNumber) => {
if (playerAddress === props.playerAddress) {
const copySpinResults = [...spinResults];
copySpinResults.push(parseInt(wheelNumber, 10));
setSpinResults(copySpinResults.slice(-20)); // Only keep the last 20 results
}
});

useEffect(() => {
// render
}, [spinResults, props.playerAddress]);
const handleExecutedWager = (playerAddress, wheelNumber, totalWinnings, totalBetsReturned) => {
if (playerAddress === props.playerAddress) {
setSpinResults(prevResults => {
const copySpinResults = [...prevResults];
copySpinResults.push(parseInt(wheelNumber, 10));
return copySpinResults.slice(-20); // Only keep the last 20 results
});
}
};

rouletteContractEvents.on('ExecutedWager', handleExecutedWager);

// Cleanup event listener
return () => {
rouletteContractEvents.off('ExecutedWager', handleExecutedWager);
};
}, [props.playerAddress]);

return (
<div
Expand Down
41 changes: 35 additions & 6 deletions src/components/roulette/NumbersHitTracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,47 @@ import { useEffect, useState } from 'react';

import { WHEEL_NUMBERS } from "../../common/wheelNumbers";

import { getPlayerNumberCompletionSetCurrent } from "../../common/blockchainWrapper";
import { getPlayerNumberCompletionSetCurrent, rouletteContractEvents } from "../../common/blockchainWrapper";

const CLASS_NAME = "NumbersHitTracker-component";
export function NumbersHitTracker(props) {
const [currentSet, setCurrentSet] = useState(new Set());

// Initialize the current set on component mount
useEffect(() => {
setTimeout(async () => {
const currentNumbers = await getPlayerNumberCompletionSetCurrent(props.playerAddress);
setCurrentSet(new Set(currentNumbers));
}, 1000);
}, [props.playerAddress, currentSet]);
const initializeSet = async () => {
try {
const currentNumbers = await getPlayerNumberCompletionSetCurrent(props.playerAddress);
setCurrentSet(new Set(currentNumbers));
} catch (error) {
console.error('Error initializing numbers hit tracker:', error);
}
};

initializeSet();
}, [props.playerAddress]);

// Listen for ExecutedWager events to update the set immediately
useEffect(() => {
const handleExecutedWager = async (playerAddress, wheelNumber, totalWinnings, totalBetsReturned) => {
if (playerAddress === props.playerAddress) {
try {
// Get the updated completion set from the blockchain
const currentNumbers = await getPlayerNumberCompletionSetCurrent(props.playerAddress);
setCurrentSet(new Set(currentNumbers));
} catch (error) {
console.error('Error updating numbers hit tracker:', error);
}
}
};

rouletteContractEvents.on('ExecutedWager', handleExecutedWager);

// Cleanup event listener
return () => {
rouletteContractEvents.off('ExecutedWager', handleExecutedWager);
};
}, [props.playerAddress]);

return (
<div
Expand Down
157 changes: 126 additions & 31 deletions src/components/roulette/Roulette.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { useEffect, useState, useCallback } from 'react';

import { PendingBet } from '../../common/PendingBet';
import { getCompleteResultsOfRound } from '../../common/getCompleteResultsOfRound';
import { getRandomWheelNumber } from '../../common/getRandomWheelNumber';
import { CompletionsCounter } from './CompletionsCounter';

import { BetResultsInfo } from './BetResultsInfo';
import { Board } from "./Board";
import { ChipSelection } from './ChipSelection';
import { ClearBetsButton } from './ClearBetsButton';
import { PendingBetsTable } from './PendingBetsTable';
import { MostRecentSpinResults } from './MostRecentSpinResults';
import { NumbersHitTracker } from './NumbersHitTracker';
Expand All @@ -21,6 +21,7 @@ import {
getPlayerAllowance,
executeWager,
placeBet,
clearBets,
getPendingBets,
rouletteContractEvents,
getBlock,
Expand Down Expand Up @@ -108,6 +109,64 @@ export function Roulette(props) {
}
}, [latestBlockNumber, refreshBalances, refreshPendingBets]);

// Set up global event listeners for consistent updates
useEffect(() => {
const handleBetPlaced = (playerAddress, betName, betAmount) => {
if (playerAddress === props.playerAddress) {
// Refresh pending bets when a bet is placed
refreshPendingBets();
refreshBalances();
}
};

const handleBetCleared = (playerAddress) => {
if (playerAddress === props.playerAddress) {
// Refresh pending bets when bets are cleared
refreshPendingBets();
refreshBalances();
}
};

const handleExecutedWager = (playerAddress, wheelNumber, totalWinnings, totalBetsReturned) => {
if (playerAddress === props.playerAddress) {
// Convert wheel number to proper format for getWinningCriteria
let parsedWheelNumber;
const numericWheelNumber = parseInt(wheelNumber, 10);
if (numericWheelNumber === 37) {
// "00" is represented as 37 in the contract
parsedWheelNumber = "00";
} else {
// Convert to string for other numbers
parsedWheelNumber = numericWheelNumber.toString();
}

// Calculate and store previous round results for display
const previousRoundResults = getCompleteResultsOfRound(
playerBalance || 0,
pendingBets,
parsedWheelNumber
);
setPreviousRoundResultsForBetResultsInfo(previousRoundResults);

// Refresh all data when wager is executed
refreshBalances();
refreshPendingBets();
}
};

// Set up event listeners
rouletteContractEvents.on('BetPlaced', handleBetPlaced);
rouletteContractEvents.on('BetCleared', handleBetCleared);
rouletteContractEvents.on('ExecutedWager', handleExecutedWager);

// Cleanup event listeners
return () => {
rouletteContractEvents.off('BetPlaced', handleBetPlaced);
rouletteContractEvents.off('BetCleared', handleBetCleared);
rouletteContractEvents.off('ExecutedWager', handleExecutedWager);
};
}, [props.playerAddress, refreshBalances, refreshPendingBets, playerBalance, pendingBets]);

function handleBettingSquareClick(bettingSquareName) {
const availableBalance = (playerBalance !== undefined ? parseFloat(playerBalance) : 0) - calculateTotalBetAmount(pendingBets);

Expand Down Expand Up @@ -157,39 +216,70 @@ export function Roulette(props) {

setWheelIsSpinning(true);

getRandomWheelNumber(`${Date.now()}${playerAddress}`)
.then(randomWheelNumber => {
const resultsOfRound = getCompleteResultsOfRound(playerBalance, pendingBets, randomWheelNumber);

setPreviousRoundResultsForBetResultsInfo(resultsOfRound);

executeWager(playerAddress)
.then((response) => response.wait()) // wait for mining first
.then((receipt) => {
// Use mined block number
setLatestBlockNumber(receipt.blockNumber);
// Refresh balances after transaction is mined
refreshBalances();
refreshPendingBets();
})
.then(() => {
rouletteContractEvents.on('ExecutedWager', (playerAddr, wheelNum, totalWinnings, totalBetsReturned) => {
if (playerAddr === props.playerAddress) {
setWheelNumber(parseInt(wheelNum, 10));
setWheelIsSpinning(false);
}
});
})
.catch((error) => {
console.error('Error executing wager:', error);
setWheelIsSpinning(false);
alert('Failed to execute wager. Please try again.');
});
// Set up event listener BEFORE starting the transaction
const handleExecutedWager = (playerAddr, wheelNum, totalWinnings, totalBetsReturned) => {
if (playerAddr === props.playerAddress) {
// Keep wheel number as number for consistency with other components
const numericWheelNumber = parseInt(wheelNum, 10);
setWheelNumber(numericWheelNumber);
setWheelIsSpinning(false);
// Remove the listener after handling the event
rouletteContractEvents.off('ExecutedWager', handleExecutedWager);
}
};

rouletteContractEvents.on('ExecutedWager', handleExecutedWager);

// Execute the wager transaction
executeWager(playerAddress)
.then((response) => response.wait()) // wait for mining
.then((receipt) => {
// Update block number and refresh data
setLatestBlockNumber(receipt.blockNumber);
refreshBalances();
refreshPendingBets();
})
.catch((error) => {
console.error('Error getting random wheel number:', error);
console.error('Error executing wager:', error);
setWheelIsSpinning(false);
alert('Failed to get random number. Please try again.');
alert('Failed to execute wager. Please try again.');
// Remove the listener on error
rouletteContractEvents.off('ExecutedWager', handleExecutedWager);
});
}

function handleClearBetsClick() {
if (pendingBets.length === 0) {
alert("No bets to clear!");
return;
}

if (wheelIsSpinning) {
alert("Cannot clear bets while wheel is spinning!");
return;
}

// Optimistically clear UI
setPendingBets([]);

// Clear bets on the blockchain
clearBets()
.then((tx) => {
console.log('Bets cleared:', tx);
return tx.wait(); // wait for mining to ensure block number is available
})
.then((receipt) => {
// Update latest block number from mined receipt
setLatestBlockNumber(receipt.blockNumber);
// Refresh balances after transaction is mined
refreshBalances();
refreshPendingBets();
})
.catch((error) => {
console.error('Error clearing bets:', error);
alert('Failed to clear bets. Please try again.');
// Restore pending bets on error
refreshPendingBets();
});
}

Expand All @@ -210,6 +300,11 @@ export function Roulette(props) {
hasABetBeenPlaced={hasABetBeenPlaced(pendingBets)}
wheelIsSpinning={wheelIsSpinning}
/>
<ClearBetsButton
onClick={() => handleClearBetsClick()}
pendingBets={pendingBets}
wheelIsSpinning={wheelIsSpinning}
/>
<SpinResult
spinResult={wheelNumber}
playerAddress={playerAddress}
Expand Down
26 changes: 18 additions & 8 deletions src/components/roulette/SpinResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,25 @@ export function SpinResult(props) {
if (props.spinResult) {
setBgColor(getWheelNumberColor(props.spinResult));
setMostRecentSpinResultText(props.spinResult === 37 ? "00" : props.spinResult);
} else {
rouletteContractEvents.on('ExecutedWager', (playerAddress, wheelNumber) => {
if (playerAddress === props.playerAddress) {
setBgColor(getWheelNumberColor(parseInt(wheelNumber, 10)));
setMostRecentSpinResultText(parseInt(wheelNumber, 10) === 37 ? "00" : parseInt(wheelNumber, 10));
}
});
}
}, [mostRecentSpinResultText, props.spinResult, props.playerAddress]);
}, [props.spinResult]);

useEffect(() => {
const handleExecutedWager = (playerAddress, wheelNumber, totalWinnings, totalBetsReturned) => {
if (playerAddress === props.playerAddress) {
const wheelNum = parseInt(wheelNumber, 10);
setBgColor(getWheelNumberColor(wheelNum));
setMostRecentSpinResultText(wheelNum === 37 ? "00" : wheelNum);
}
};

rouletteContractEvents.on('ExecutedWager', handleExecutedWager);

// Cleanup event listener
return () => {
rouletteContractEvents.off('ExecutedWager', handleExecutedWager);
};
}, [props.playerAddress]);

return (
<div
Expand Down
11 changes: 11 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,17 @@ because we'll have "invisible" betting options later
align-items: center;
}

.ClearBetsButton-component {
position: relative;
left: 900px;
top: -80px;
width: 200px;
height: 60px;
display: flex;
justify-content: center;
align-items: center;
}

.SpinResult-component {
position: relative;
left: 1120px;
Expand Down
Loading