Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MISC: Validate bet input of casino mini games #1694

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
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
85 changes: 85 additions & 0 deletions src/Casino/BetInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import TextField from "@mui/material/TextField";
import { Player } from "@player";
import React, { useState } from "react";
import { Settings } from "../Settings/Settings";
import { formatMoney } from "../ui/formatNumber";

export interface BetInputProps {
initialBet: number;
maxBet: number;
gameInProgress: boolean;
setBet: (bet: number) => void;
validBetCallback?: () => void;
invalidBetCallback?: () => void;
}

export function BetInput({
initialBet,
maxBet,
gameInProgress,
setBet,
validBetCallback,
invalidBetCallback,
}: BetInputProps): React.ReactElement {
const [betValue, setBetValue] = useState<string>(initialBet.toString());
const [helperText, setHelperText] = useState<string>("");
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const betInput = event.target.value;
setBetValue(betInput);
const bet = Math.round(parseFloat(betInput));
let isValid = false;
if (isNaN(bet)) {
setBet(0);
setHelperText("Not a valid number");
} else if (bet <= 0) {
setBet(0);
setHelperText("Must bet a positive amount");
} else if (bet > maxBet) {
setBet(0);
setHelperText("Exceed max bet");
} else if (!Player.canAfford(bet)) {
setBet(0);
setHelperText("Not enough money");
} else {
// Valid wager
isValid = true;
setBet(bet);
setHelperText("");
}
if (isValid) {
if (validBetCallback) {
validBetCallback();
}
} else {
if (invalidBetCallback) {
invalidBetCallback();
}
}
};
return (
<>
<TextField
sx={{
marginTop: "20px",
marginBottom: "20px",
"& .MuiInputLabel-root.Mui-disabled": {
WebkitTextFillColor: Settings.theme.disabled,
},
"& .MuiInputBase-input.Mui-disabled": {
WebkitTextFillColor: Settings.theme.disabled,
},
}}
value={betValue}
label={<>Wager (Max: {formatMoney(maxBet)})</>}
disabled={gameInProgress}
onChange={onChange}
error={helperText !== ""}
helperText={helperText}
type="number"
style={{
width: "200px",
}}
/>
</>
);
}
152 changes: 61 additions & 91 deletions src/Casino/Blackjack.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import * as React from "react";

import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Paper from "@mui/material/Paper";
import Typography from "@mui/material/Typography";
import { Player } from "@player";
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Money } from "../ui/React/Money";
import { win, reachedLimit } from "./Game";
import { BetInput } from "./BetInput";
import { Deck } from "./CardDeck/Deck";
import { Hand } from "./CardDeck/Hand";
import { InputAdornment } from "@mui/material";
import { ReactCard } from "./CardDeck/ReactCard";
import Button from "@mui/material/Button";
import Paper from "@mui/material/Paper";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import { reachedLimit, win } from "./Game";

const initialBet = 1e6;
const maxBet = 100e6;

const MAX_BET = 100e6;
export const DECK_COUNT = 5; // 5-deck multideck

enum Result {
Pending = "",
Pending = "Pending",
PlayerWon = "You won!",
PlayerWonByBlackjack = "You Won! Blackjack!",
DealerWon = "You lost!",
Expand All @@ -44,8 +46,6 @@ export class Blackjack extends React.Component<Record<string, never>, State> {

this.deck = new Deck(DECK_COUNT);

const initialBet = 1e6;

this.state = {
playerHand: new Hand([]),
dealerHand: new Hand([]),
Expand Down Expand Up @@ -203,18 +203,37 @@ export class Blackjack extends React.Component<Record<string, never>, State> {
};

finishGame = (result: Result): void => {
const gains =
result === Result.DealerWon
? 0 // We took away the bet at the start, don't need to take more
: result === Result.Tie
? this.state.bet // We took away the bet at the start, give it back
: result === Result.PlayerWon
? 2 * this.state.bet // Give back their bet plus their winnings
: result === Result.PlayerWonByBlackjack
? 2.5 * this.state.bet // Blackjack pays out 1.5x bet!
: (() => {
throw new Error(`Unexpected result: ${result}`);
})(); // This can't happen, right?
/**
* Explicitly declare the type of "gains". If we forget a case here, TypeScript will notify us: "Variable 'gains' is
* used before being assigned.".
*/
let gains: number;
switch (result) {
case Result.DealerWon:
// We took away the bet at the start, don't need to take more
gains = 0;
break;
case Result.Tie:
// We took away the bet at the start, give it back
gains = this.state.bet;
break;
case Result.PlayerWon:
// Give back their bet plus their winnings
gains = 2 * this.state.bet;
break;
case Result.PlayerWonByBlackjack:
// Blackjack pays out 1.5x bet!
gains = 2.5 * this.state.bet;
break;
case Result.Pending:
/**
* Don't throw an error. Callers of this function are event handlers (onClick) of buttons. If we throw an error,
* it won't be shown to the player.
*/
dialogBoxCreate(`Unexpected Blackjack result: ${result}. This is a bug. Please contact the developer.`);
gains = 0;
break;
}
win(gains);
this.setState({
gameInProgress: false,
Expand All @@ -223,49 +242,6 @@ export class Blackjack extends React.Component<Record<string, never>, State> {
});
};

wagerOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
const betInput = event.target.value;
const wager = Math.round(parseFloat(betInput));
if (isNaN(wager)) {
this.setState({
bet: 0,
betInput,
wagerInvalid: true,
wagerInvalidHelperText: "Not a valid number",
});
} else if (wager <= 0) {
this.setState({
bet: 0,
betInput,
wagerInvalid: true,
wagerInvalidHelperText: "Must bet a positive amount",
});
} else if (wager > MAX_BET) {
this.setState({
bet: 0,
betInput,
wagerInvalid: true,
wagerInvalidHelperText: "Exceeds max bet",
});
} else if (!Player.canAfford(wager)) {
this.setState({
bet: 0,
betInput,
wagerInvalid: true,
wagerInvalidHelperText: "Not enough money",
});
} else {
// Valid wager
this.setState({
bet: wager,
betInput,
wagerInvalid: false,
wagerInvalidHelperText: "",
result: Result.Pending, // Reset previous game status to clear the win/lose text UI
});
}
};

// Start game button
startOnClick = (event: React.MouseEvent): void => {
// Protect against scripting...although maybe this would be fun to automate
Expand All @@ -279,40 +255,34 @@ export class Blackjack extends React.Component<Record<string, never>, State> {
};

render(): React.ReactNode {
const { betInput, playerHand, dealerHand, gameInProgress, result, wagerInvalid, wagerInvalidHelperText, gains } =
this.state;
const { playerHand, dealerHand, gameInProgress, result, wagerInvalid, gains } = this.state;

// Get the player totals to display.
const playerHandValues = this.getHandDisplayValues(playerHand);
const dealerHandValues = this.getHandDisplayValues(dealerHand);

return (
<>
{/* Wager input */}
<Box>
<TextField
value={betInput}
label={
<>
{"Wager (Max: "}
<Money money={MAX_BET} />
{")"}
</>
}
disabled={gameInProgress}
onChange={this.wagerOnChange}
error={wagerInvalid}
helperText={wagerInvalid ? wagerInvalidHelperText : ""}
type="number"
style={{
width: "200px",
<BetInput
initialBet={initialBet}
maxBet={maxBet}
gameInProgress={gameInProgress}
setBet={(bet) => {
this.setState({
bet,
});
}}
validBetCallback={() => {
this.setState({
wagerInvalid: false,
result: Result.Pending,
});
}}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Typography>$</Typography>
</InputAdornment>
),
invalidBetCallback={() => {
this.setState({
wagerInvalid: true,
});
}}
/>

Expand Down
Loading