# Blackjack and Burn

## Instructions

* Approve [AFFECTION™](/docs/token-contracts/affection-tm-a.md) with spender 0x1d51a6D59D6C1129492527977581705eE988BFA5
* Deposit [AFFECTION™](/docs/token-contracts/affection-tm-a.md) using playerDeposit()
* Start a game by calling dealNewHand() with your bet amount
* Hit or Stand, try to get 21 or as close as possible without going over
* Aces will count when the player or dealer begins to bust

{% hint style="info" %}
Payouts are 3:2
{% endhint %}

{% hint style="info" %}
Max bet allowed is 1-500 tokens for now
{% endhint %}

## Features

* True RNG using libAtropaMath v1.1
* Total wins/losses/burns/incentives tracking
* Burn contract wins + incentivize callers
* Events emitted for every player action allows for easy implementation with Ethers.js

## Demo

* <https://affection.icu/blackjack/>
* <https://affection.pages.dev/blackjack/>

## Contract Address

0x1d51a6D59D6C1129492527977581705eE988BFA5

## Contract Code

{% tabs %}
{% tab title="ABI" %}
{% code lineNumbers="true" %}

```json
[
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "_minBet",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "_maxBet",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "_incentivesPercent",
				"type": "uint256"
			}
		],
		"stateMutability": "nonpayable",
		"type": "constructor"
	},
	{
		"inputs": [],
		"name": "DoubledDown",
		"type": "error"
	},
	{
		"inputs": [],
		"name": "GameEnded",
		"type": "error"
	},
	{
		"inputs": [],
		"name": "GameStarted",
		"type": "error"
	},
	{
		"inputs": [],
		"name": "InsufficientAllowance",
		"type": "error"
	},
	{
		"inputs": [],
		"name": "InsufficientBankReserveBalance",
		"type": "error"
	},
	{
		"inputs": [],
		"name": "InsufficientPlayerBalance",
		"type": "error"
	},
	{
		"inputs": [],
		"name": "InsufficientPlayerWalletBalance",
		"type": "error"
	},
	{
		"inputs": [],
		"name": "InvalidBet",
		"type": "error"
	},
	{
		"inputs": [],
		"name": "NoReentry",
		"type": "error"
	},
	{
		"inputs": [],
		"name": "NothingToBurn",
		"type": "error"
	},
	{
		"inputs": [],
		"name": "Standing",
		"type": "error"
	},
	{
		"inputs": [],
		"name": "TooLate",
		"type": "error"
	},
	{
		"inputs": [],
		"name": "Unauthorized",
		"type": "error"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "value",
				"type": "uint256"
			}
		],
		"name": "Blackjack",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "value",
				"type": "uint256"
			}
		],
		"name": "Burn",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "value",
				"type": "uint256"
			}
		],
		"name": "DealNewHand",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "value",
				"type": "uint256"
			}
		],
		"name": "Deposit",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "value",
				"type": "uint256"
			}
		],
		"name": "DoubleDown",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			}
		],
		"name": "Hit",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "value",
				"type": "uint256"
			}
		],
		"name": "Incentives",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "value",
				"type": "uint256"
			}
		],
		"name": "Loss",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			}
		],
		"name": "Push",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			}
		],
		"name": "Stand",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "value",
				"type": "uint256"
			}
		],
		"name": "Surrender",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "value",
				"type": "uint256"
			}
		],
		"name": "Win",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "value",
				"type": "uint256"
			}
		],
		"name": "Withdraw",
		"type": "event"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "_address",
				"type": "address"
			},
			{
				"internalType": "uint256",
				"name": "_amount",
				"type": "uint256"
			}
		],
		"name": "bankWithdraw",
		"outputs": [],
		"stateMutability": "payable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "_amount",
				"type": "uint256"
			}
		],
		"name": "bankWithdrawPLS",
		"outputs": [],
		"stateMutability": "payable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "burn",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "_RNGContractAddress",
				"type": "address"
			},
			{
				"internalType": "uint256",
				"name": "_minBet",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "_maxBet",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "_burnMode",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "_burnIncentivePercent",
				"type": "uint256"
			}
		],
		"name": "config",
		"outputs": [],
		"stateMutability": "payable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "_betAmount",
				"type": "uint256"
			}
		],
		"name": "dealNewHand",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "doubleDown",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "hit",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "string",
				"name": "",
				"type": "string"
			}
		],
		"name": "house",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "playerBalance",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "_amount",
				"type": "uint256"
			}
		],
		"name": "playerDeposit",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "_amount",
				"type": "uint256"
			}
		],
		"name": "playerWithdraw",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "_address",
				"type": "address"
			}
		],
		"name": "setOwner",
		"outputs": [],
		"stateMutability": "payable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "stand",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "surrender",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "viewGameState",
		"outputs": [
			{
				"components": [
					{
						"internalType": "int256",
						"name": "result",
						"type": "int256"
					},
					{
						"internalType": "uint256",
						"name": "round",
						"type": "uint256"
					},
					{
						"internalType": "string[2][]",
						"name": "dealerCards",
						"type": "string[2][]"
					},
					{
						"internalType": "uint256",
						"name": "dealerScore",
						"type": "uint256"
					},
					{
						"internalType": "uint256",
						"name": "playerBet",
						"type": "uint256"
					},
					{
						"internalType": "string[2][]",
						"name": "playerCards",
						"type": "string[2][]"
					},
					{
						"internalType": "uint256",
						"name": "playerScore",
						"type": "uint256"
					},
					{
						"internalType": "uint256",
						"name": "playerDoubleDown",
						"type": "uint256"
					},
					{
						"internalType": "uint256",
						"name": "playerStand",
						"type": "uint256"
					}
				],
				"internalType": "struct BlackjackAndBurn.gameState",
				"name": "",
				"type": "tuple"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"stateMutability": "payable",
		"type": "receive"
	}
]
```

{% endcode %}
{% endtab %}

{% tab title="Solidity" %}
{% code lineNumbers="true" %}

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

interface IERC20 {
    function allowance(address _owner, address _spender) external view returns (uint256);
    function approve(address _spender, uint256 _value) external returns (bool);
    function balanceOf(address _owner) external view returns (uint256);
    function decimals() external view returns (uint256);
    function transferFrom(address _sender, address _recipient, uint256 _value ) external returns (bool);
    function transfer(address _recipient, uint256 _value) external returns (bool);
}

interface IRNG {
    function Generate() external returns (uint64);
}

error DoubledDown();
error GameEnded();
error GameStarted();
error InsufficientAllowance();
error InsufficientBankReserveBalance();
error InsufficientPlayerBalance();
error InsufficientPlayerWalletBalance();
error InvalidBet();
error NoReentry();
error NothingToBurn();
error Standing();
error TooLate();
error Unauthorized();

contract BlackjackAndBurn {
    uint256 internal locked;
    address ownerAddress;
    mapping(address => int256) gameResult;
    mapping(address => mapping(string => uint256)) gameData;
    mapping(address => mapping(string => string[2][])) gameHands;
    mapping(string => uint256) public house;
    mapping(string => string[]) cards;
    mapping(string => uint256) cardValues;
    address RNGContractAddress = 0xa96BcbeD7F01de6CEEd14fC86d90F21a36dE2143;
    address currencyContractAddress = 0x24F0154C1dCe548AdF15da2098Fdd8B8A3B8151D;
    IRNG RNGContract = IRNG(RNGContractAddress);
    IERC20 currencyContract = IERC20(currencyContractAddress);

    event Blackjack(address indexed from, uint256 value);
    event Burn(address indexed from, uint256 value);
    event DealNewHand(address indexed from, uint256 value);
    event Deposit(address indexed from, uint256 value);
    event DoubleDown(address indexed from, uint256 value);
    event Hit(address indexed from);
    event Incentives(address indexed from, uint256 value);
    event Loss(address indexed from, uint256 value);
    event Push(address indexed from);
    event Stand(address indexed from);
    event Surrender(address indexed from, uint256 value);
    event Withdraw(address indexed from, uint256 value);
    event Win(address indexed from, uint256 value);

    struct gameState {
        int256 result;
        uint256 round;
        string[2][] dealerCards;
        uint256 dealerScore;
        uint256 playerBet;
        string[2][] playerCards;
        uint256 playerScore;
        uint256 playerDoubleDown;
        uint256 playerStand;
    }

    constructor(
        uint256 _minBet,
        uint256 _maxBet,
        uint256 _incentivesPercent
    ) {
        ownerAddress = msg.sender;
        house["payoutWin"] = 100;
        house["payoutBlackjack"] = 150;
        house["minBet"] = _minBet;
        house["maxBet"] = _maxBet;
        house["wins"] = 0;
        house["losses"] = 0;
        house["burns"] = 0;
        house["burnsMode"] = 1;
        house["incentives"] = 0;
        house["incentivesPercent"] = _incentivesPercent;
        house["currencyDecimals"] = currencyContract.decimals();
        cards["ranks"] = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"];
        cards["ranksUpT"] = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"];
        cards["ranksUpA"] = ["2", "3", "4", "5", "6", "7", "8", "9"];
        cards["suits"] = [unicode"♠", unicode"♥", unicode"♣", unicode"♦"];
        cardValues["2"] = 2;cardValues["3"] = 3; cardValues["4"] = 4; cardValues["5"] = 5; cardValues["6"] = 6; cardValues["7"] = 7; cardValues["8"] = 8; cardValues["9"] = 9; cardValues["10"] = 10; cardValues["J"] = 10; cardValues["Q"] = 10; cardValues["K"] = 10; cardValues["A"] = 11;
    }

    receive() external payable {}

    modifier reentrantLock() {
        if (locked != 0)
            revert NoReentry();
        locked = 1;
        _;
        locked = 0;
    }

    modifier onlyOwner() {
        if (msg.sender != ownerAddress)
            revert Unauthorized();
        _;
    }

    function setOwner(address _address) public payable onlyOwner {
        // transfer ownership
        ownerAddress = _address;
    }

    function bankWithdrawPLS(uint256 _amount) public payable onlyOwner {
        // withdraw pls from the contract
        if (address(this).balance < _amount)
            revert InsufficientBankReserveBalance();
        payable(address(msg.sender)).transfer(_amount);
    }

    function bankWithdraw(address _address, uint256 _amount) public payable onlyOwner {
        // withdraw tokens from the contract
        IERC20 token = IERC20(_address);
        if (token.balanceOf(address(this)) < _amount)
            revert InsufficientBankReserveBalance();
        token.transfer(msg.sender, _amount);
    }

    function dealCard(uint256 _postBlackjackCheck) internal returns (string[2] memory) {
        string memory pile;
        // dealer's up card is an ace
        if (_postBlackjackCheck == 11) {
            pile = "ranksUpA";
        // dealer's up card is a 10
        } else if (_postBlackjackCheck == 10) {
            pile = "ranksUpT";
        // normal deck
        } else {
            pile = "ranks";
        }
        uint64 rng = RNGContract.Generate();
        return [
            cards[pile][(rng % cards[pile].length)],
            cards["suits"][(rng % cards["suits"].length)]
        ];
    }

    function burn() public {
        // get burnable amount
        int256 burnable = int256(house["wins"]) - int256(house["burns"]) - int256(house["incentives"]);
        // check if net burn is enabled
        if (house["burnsMode"] == 1)
            burnable -= int256(house["losses"]);
        // only burn if positive number
        if (burnable <= 0)
            revert NothingToBurn();
        // burn any amount above zero
        uint256 incentives = (uint256(burnable) * house["incentivesPercent"]) / 100;
        uint256 burning = uint256(burnable) - incentives;
        unchecked { house["burns"] += burning; }
        unchecked { house["incentives"] += incentives; }
        currencyContract.transfer(msg.sender, incentives);
        currencyContract.transfer(address(0x000000000000000000000000000000000000dEaD), burning);
        emit Burn(msg.sender, burning);
        emit Incentives(msg.sender, incentives);
    }

    function calculatePayout(uint256 _value, uint256 _percent) internal pure returns (uint256) {
        // amount with percent bonus
        return _value + ((_value * _percent) / 100);
    }

    function config(address _RNGContractAddress, uint256 _minBet, uint256 _maxBet, uint256 _burnMode, uint256 _burnIncentivePercent) public payable onlyOwner {
        // change rng contract address
        if (_RNGContractAddress != address(0))
            RNGContractAddress = _RNGContractAddress;
        // change min/max bet amounts
        if (_minBet != 0)
            house["minBet"] = _minBet;
        if (_maxBet != 0)
            house["maxBet"] = _maxBet;
        // change burn mode and incentive percent
        if (_burnMode != 0)
            house["burnsMode"] = _burnMode;
        if (_burnIncentivePercent != 0)
            house["incentivesPercent"] = _burnIncentivePercent;
    }

    function countAces(string memory _who) internal returns (bool) {
        // subtract one ace at a time until the score is below or equal to 21
        string memory whoAces = string.concat(_who, "Aces");
        uint256 aces = gameData[msg.sender][whoAces];
        for (uint256 a; a < aces; ++a) {
            gameData[msg.sender][whoAces] -= 1;
            gameData[msg.sender][_who] -= 10;
            if (gameData[msg.sender][_who] <= 21)
                break;
        }
        // not enough aces
        if (gameData[msg.sender][_who] > 21) {
            if (keccak256(bytes(_who)) == keccak256(bytes("Dealer"))) {
                // dealer busts, player wins
                endGame(1);
            } else {
                // player busts, dealer wins
                endGame(-1);
            }
            return false;
        }
        return true;
    }

    function dealCardTo(string memory _who) internal returns (bool) {
        // skip if dealing to dealer and dealer has enough
        string[2] memory card;
        if (keccak256(bytes(_who)) == keccak256(bytes("Dealer"))) {
            if (gameData[msg.sender][_who] >= 17) {
                // empty card placeholder
                gameHands[msg.sender]["Dealer"].push(["", ""]);
                return true;
            }
            // dealer failed natural blackjack and should be given a different card
            card = dealCard(gameData[msg.sender]["Dealer"]);
        } else {
            card = dealCard(0);
        }
        // put card in hand
        gameHands[msg.sender][_who].push(card);
        // count if ace was dealt
        uint256 cardValue = cardValues[card[0]];
        if (cardValue == 11) {
            unchecked { gameData[msg.sender][string.concat(_who, "Aces")] += 1; }
        }
        // count card value
        unchecked { gameData[msg.sender][_who] += cardValue; }
        if (gameData[msg.sender][_who] > 21)
            return false;
        return true;
    }

    function dealNewHand(uint256 _betAmount) public reentrantLock {
        if (gameResult[msg.sender] == 255)
            revert GameStarted();
        if (_betAmount < house["minBet"] || _betAmount > house["maxBet"])
            revert InvalidBet();
        if (gameData[msg.sender]["Bank"] < _betAmount)
            revert InsufficientPlayerWalletBalance();
        gameData[msg.sender]["Round"] = 1;
        gameData[msg.sender]["Stand"] = 0;
        // activate game state for player
        gameResult[msg.sender] = 255;
        // remember how much the player has bet
        gameData[msg.sender]["Bet"] = _betAmount;
        // remove the amount from the player's bank balance
        gameData[msg.sender]["Bank"] -= _betAmount;
        // player is dealt 1 card
        dealNewHandTo("You");
        // dealer is dealt 1 card
        dealNewHandTo("Dealer");
        // player is dealt another card
        dealCardTo("You");
        emit DealNewHand(msg.sender, _betAmount);
        // check if player was dealt a blackjack
        if (gameData[msg.sender]["You"] >= 21) {
            gameData[msg.sender]["You"] = 21;
            // flip dealer's hole card
            if (dealCardTo("Dealer")) {
                if (gameData[msg.sender]["Dealer"] < 21)
                    // dealer failed to push
                    endGame(2);
            } else {
                // it's a draw
                gameData[msg.sender]["Dealer"] = 21;
                endGame(0);
            }
        } else {
            // roll to check if dealer got a natural blackjack
            string[2] memory card = dealCard(0);
            if (gameData[msg.sender]["Dealer"] + cardValues[card[0]] >= 21) {
                gameData[msg.sender]["Dealer"] = 21;
                gameHands[msg.sender]["Dealer"].push(card);
                endGame(-1);
            }
        }
    }

    function dealNewHandTo(string memory _who) internal {
        // set the initial game state
        gameHands[msg.sender][_who] = [dealCard(0)];
        string memory whoAces = string.concat(_who, "Aces");
        gameData[msg.sender][whoAces] = 0;
        gameData[msg.sender]["DoubleDown"] = 0;
        // count if ace was dealt
        uint256 cardValue = cardValues[gameHands[msg.sender][_who][0][0]];
        if (cardValue == 11) {
            unchecked { gameData[msg.sender][whoAces] += 1; }
        }
        gameData[msg.sender][_who] = cardValue;
    }

    function doubleDown() public {
        // double the player's bet
        uint256 bet = gameData[msg.sender]["Bet"];
        if (gameResult[msg.sender] != 255)
            revert GameEnded();
        if (gameData[msg.sender]["Round"] != 1)
            revert TooLate();
        if (gameData[msg.sender]["Bank"] < bet)
            revert InsufficientPlayerWalletBalance();
        gameData[msg.sender]["Bank"] -= bet;
        gameData[msg.sender]["Bet"] = bet * 2;
        gameData[msg.sender]["DoubleDown"] = 1;
        emit DoubleDown(msg.sender, gameData[msg.sender]["Bet"]);
        // take a card
        hit();
    }

    function endGame(int256 _result) internal {
        // end the game and calculate rewards
        uint256 payout;
        if (_result == 2) {
            // blackjack
            payout = calculatePayout(
                gameData[msg.sender]["Bet"],
                house[string.concat("payoutBlackjack")]
            );
            unchecked { house["losses"] += payout; }
            emit Blackjack(msg.sender, payout);
        } else if (_result == 1) {
            // win
            payout = calculatePayout(
                gameData[msg.sender]["Bet"],
                house[string.concat("payoutWin")]
            );
            unchecked { house["losses"] += payout; }
            emit Win(msg.sender, payout);
        } else if (_result == 0) {
            // push
            payout = gameData[msg.sender]["Bet"];
            emit Push(msg.sender);
        } else if (_result == -1) {
            // loss
            payout = 0;
            unchecked { house["wins"] += gameData[msg.sender]["Bet"]; }
            emit Loss(msg.sender, gameData[msg.sender]["Bet"]);
        } else if (_result == -2) {
            // surrender
            payout = gameData[msg.sender]["Bet"] / 2;
            unchecked { house["wins"] += payout; }
            emit Surrender(msg.sender, payout);
        }
        // re-add balance to player's contract wallet if they won
        if (payout != 0) {
            unchecked { gameData[msg.sender]["Bank"] += payout; }
        }
        // store the result of the game
        gameResult[msg.sender] = _result;
    }

    function hit() public {
        if (gameResult[msg.sender] != 255)
            revert GameEnded();
        if (gameData[msg.sender]["Stand"] != 0)
            revert Standing();
        if (gameData[msg.sender]["DoubleDown"] != 0)
            if (gameData[msg.sender]["Round"] > 1)
                revert DoubledDown();
        unchecked { gameData[msg.sender]["Round"] += 1; }
        emit Hit(msg.sender);
        // check if dealer's first turn and flip their hole card
        if (gameHands[msg.sender]["Dealer"].length == 1)
            dealCardTo("Dealer");
        // deal cards
        string[2] memory players = ["You", "Dealer"];
        for (uint256 p; p < players.length; ++p)
            if (!dealCardTo(players[p]))
                if (!countAces(players[p]))
                    return;
        // calculate the score
        scoreGame();
    }

    function playerBalance() public view returns (uint256) {
        // show player's deposits and wins/losses
        return gameData[msg.sender]["Bank"];
    }

    function playerDeposit(uint256 _amount) public reentrantLock {
        // deposit player's tokens to the contract
        if (currencyContract.allowance(msg.sender, address(this)) < _amount)
            revert InsufficientAllowance();
        if (currencyContract.balanceOf(msg.sender) < _amount)
            revert InsufficientPlayerBalance();
        // transfer inbound
        currencyContract.transferFrom(msg.sender, address(this), _amount);
        unchecked { gameData[msg.sender]["Bank"] += _amount; }
        emit Deposit(msg.sender, _amount);
    }

    function playerWithdraw(uint256 _amount) public reentrantLock {
        // withdraw player's tokens from the contract
        if (currencyContract.balanceOf(address(this)) < _amount)
            revert InsufficientBankReserveBalance();
        if (gameData[msg.sender]["Bank"] < _amount)
            revert InsufficientPlayerWalletBalance();
        // transfer outbound
        currencyContract.transfer(msg.sender, _amount);
        gameData[msg.sender]["Bank"] -= _amount;
        emit Withdraw(msg.sender, _amount);
    }

    function scoreGame() internal {
        // player stands
        if (gameData[msg.sender]["Stand"] == 1) {
            // dealer stands
            if (gameData[msg.sender]["Dealer"] >= 17)
                // player has won
                if (gameData[msg.sender]["You"] > gameData[msg.sender]["Dealer"])
                    endGame(1);
            // game ends in a push
            if (gameData[msg.sender]["Dealer"] == gameData[msg.sender]["You"])
                endGame(0);
            // dealer has won
            if (gameData[msg.sender]["Dealer"] > gameData[msg.sender]["You"])
                endGame(-1);
        } else {
            // dealer busts
            if (gameData[msg.sender]["Dealer"] > 21)
                endGame(1);
            // player busts
            if (gameData[msg.sender]["You"] > 21)
                endGame(-1);
            // dealer stands
            if (gameData[msg.sender]["Dealer"] >= 17) {
                // player wins
                if (gameData[msg.sender]["You"] > gameData[msg.sender]["Dealer"])
                    endGame(1);
                // game ends in a push
                if (gameData[msg.sender]["You"] == gameData[msg.sender]["Dealer"])
                    endGame(0);
                // player cannot hit anymore
                if (gameData[msg.sender]["DoubleDown"] == 1)
                    // dealer wins
                    if (gameData[msg.sender]["Dealer"] > gameData[msg.sender]["You"])
                        endGame(-1);
            }
        }
    }

    function stand() public {
        if (gameResult[msg.sender] != 255)
            revert GameEnded();
        unchecked { gameData[msg.sender]["Round"] += 1; }
        gameData[msg.sender]["Stand"] = 1;
        emit Stand(msg.sender);
        // empty card placeholder
        gameHands[msg.sender]["You"].push(["", ""]);
        // check if dealer's first turn and flip their hole card
        if (gameHands[msg.sender]["Dealer"].length == 1)
            dealCardTo("Dealer");
        // calculate score
        scoreGame();
        // deal card to dealer
        if (!dealCardTo("Dealer"))
            if (!countAces("Dealer"))
                return;
        // calculate score
        scoreGame();
    }

    function surrender() public {
        if (gameResult[msg.sender] != 255)
            revert GameEnded();
        if (gameData[msg.sender]["Round"] != 1)
            revert TooLate();
        // player surrenders half of their bet
        endGame(-2);
    }

    function viewGameState() public view returns (gameState memory) {
        // state of the game
        return gameState({
            result: gameResult[msg.sender],
            round: gameData[msg.sender]["Round"],
            dealerCards: gameHands[msg.sender]["Dealer"],
            dealerScore: gameData[msg.sender]["Dealer"],
            playerBet: gameData[msg.sender]["Bet"],
            playerCards: gameHands[msg.sender]["You"],
            playerDoubleDown: gameData[msg.sender]["DoubleDown"],
            playerScore: gameData[msg.sender]["You"],
            playerStand: gameData[msg.sender]["Stand"]
        });
    }
}

```

{% endcode %}
{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://affection.gitbook.io/docs/use-cases/example-dapps/blackjack-and-burn.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
