309 lines
8.4 KiB
Solidity
309 lines
8.4 KiB
Solidity
|
/*
|
||
|
* @article: https://blog.positive.com/predicting-random-numbers-in-ethereum-smart-contracts-e5358c6b8620
|
||
|
* @source: https://etherscan.io/address/0xa65d59708838581520511d98fb8b5d1f76a96cad#code
|
||
|
* @vulnerable_at_lines: 17,19,21
|
||
|
* @author: -
|
||
|
*/
|
||
|
|
||
|
pragma solidity ^0.4.2;
|
||
|
|
||
|
library Deck {
|
||
|
// returns random number from 0 to 51
|
||
|
// let's say 'value' % 4 means suit (0 - Hearts, 1 - Spades, 2 - Diamonds, 3 - Clubs)
|
||
|
// 'value' / 4 means: 0 - King, 1 - Ace, 2 - 10 - pip values, 11 - Jacket, 12 - Queen
|
||
|
|
||
|
function deal(address player, uint8 cardNumber) internal returns (uint8) {
|
||
|
// <yes> <report> BAD_RANDOMNESS
|
||
|
uint b = block.number;
|
||
|
// <yes> <report> BAD_RANDOMNESS
|
||
|
uint timestamp = block.timestamp;
|
||
|
// <yes> <report> BAD_RANDOMNESS
|
||
|
return uint8(uint256(keccak256(block.blockhash(b), player, cardNumber, timestamp)) % 52);
|
||
|
}
|
||
|
|
||
|
function valueOf(uint8 card, bool isBigAce) internal constant returns (uint8) {
|
||
|
uint8 value = card / 4;
|
||
|
if (value == 0 || value == 11 || value == 12) { // Face cards
|
||
|
return 10;
|
||
|
}
|
||
|
if (value == 1 && isBigAce) { // Ace is worth 11
|
||
|
return 11;
|
||
|
}
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
function isAce(uint8 card) internal constant returns (bool) {
|
||
|
return card / 4 == 1;
|
||
|
}
|
||
|
|
||
|
function isTen(uint8 card) internal constant returns (bool) {
|
||
|
return card / 4 == 10;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
contract BlackJack {
|
||
|
using Deck for *;
|
||
|
|
||
|
uint public minBet = 50 finney; // 0.05 eth
|
||
|
uint public maxBet = 5 ether;
|
||
|
|
||
|
uint8 BLACKJACK = 21;
|
||
|
|
||
|
enum GameState { Ongoing, Player, Tie, House }
|
||
|
|
||
|
struct Game {
|
||
|
address player; // address игрока
|
||
|
uint bet; // стывка
|
||
|
|
||
|
uint8[] houseCards; // карты диллера
|
||
|
uint8[] playerCards; // карты игрока
|
||
|
|
||
|
GameState state; // состояние
|
||
|
uint8 cardsDealt;
|
||
|
}
|
||
|
|
||
|
mapping (address => Game) public games;
|
||
|
|
||
|
modifier gameIsGoingOn() {
|
||
|
if (games[msg.sender].player == 0 || games[msg.sender].state != GameState.Ongoing) {
|
||
|
throw; // game doesn't exist or already finished
|
||
|
}
|
||
|
_;
|
||
|
}
|
||
|
|
||
|
event Deal(
|
||
|
bool isUser,
|
||
|
uint8 _card
|
||
|
);
|
||
|
|
||
|
event GameStatus(
|
||
|
uint8 houseScore,
|
||
|
uint8 houseScoreBig,
|
||
|
uint8 playerScore,
|
||
|
uint8 playerScoreBig
|
||
|
);
|
||
|
|
||
|
event Log(
|
||
|
uint8 value
|
||
|
);
|
||
|
|
||
|
function BlackJack() {
|
||
|
|
||
|
}
|
||
|
|
||
|
function () payable {
|
||
|
|
||
|
}
|
||
|
|
||
|
// starts a new game
|
||
|
function deal() public payable {
|
||
|
if (games[msg.sender].player != 0 && games[msg.sender].state == GameState.Ongoing) {
|
||
|
throw; // game is already going on
|
||
|
}
|
||
|
|
||
|
if (msg.value < minBet || msg.value > maxBet) {
|
||
|
throw; // incorrect bet
|
||
|
}
|
||
|
|
||
|
uint8[] memory houseCards = new uint8[](1);
|
||
|
uint8[] memory playerCards = new uint8[](2);
|
||
|
|
||
|
// deal the cards
|
||
|
playerCards[0] = Deck.deal(msg.sender, 0);
|
||
|
Deal(true, playerCards[0]);
|
||
|
houseCards[0] = Deck.deal(msg.sender, 1);
|
||
|
Deal(false, houseCards[0]);
|
||
|
playerCards[1] = Deck.deal(msg.sender, 2);
|
||
|
Deal(true, playerCards[1]);
|
||
|
|
||
|
games[msg.sender] = Game({
|
||
|
player: msg.sender,
|
||
|
bet: msg.value,
|
||
|
houseCards: houseCards,
|
||
|
playerCards: playerCards,
|
||
|
state: GameState.Ongoing,
|
||
|
cardsDealt: 3
|
||
|
});
|
||
|
|
||
|
checkGameResult(games[msg.sender], false);
|
||
|
}
|
||
|
|
||
|
// deals one more card to the player
|
||
|
function hit() public gameIsGoingOn {
|
||
|
uint8 nextCard = games[msg.sender].cardsDealt;
|
||
|
games[msg.sender].playerCards.push(Deck.deal(msg.sender, nextCard));
|
||
|
games[msg.sender].cardsDealt = nextCard + 1;
|
||
|
Deal(true, games[msg.sender].playerCards[games[msg.sender].playerCards.length - 1]);
|
||
|
checkGameResult(games[msg.sender], false);
|
||
|
}
|
||
|
|
||
|
// finishes the game
|
||
|
function stand() public gameIsGoingOn {
|
||
|
|
||
|
var (houseScore, houseScoreBig) = calculateScore(games[msg.sender].houseCards);
|
||
|
|
||
|
while (houseScoreBig < 17) {
|
||
|
uint8 nextCard = games[msg.sender].cardsDealt;
|
||
|
uint8 newCard = Deck.deal(msg.sender, nextCard);
|
||
|
games[msg.sender].houseCards.push(newCard);
|
||
|
games[msg.sender].cardsDealt = nextCard + 1;
|
||
|
houseScoreBig += Deck.valueOf(newCard, true);
|
||
|
Deal(false, newCard);
|
||
|
}
|
||
|
|
||
|
checkGameResult(games[msg.sender], true);
|
||
|
}
|
||
|
|
||
|
// @param finishGame - whether to finish the game or not (in case of Blackjack the game finishes anyway)
|
||
|
function checkGameResult(Game game, bool finishGame) private {
|
||
|
// calculate house score
|
||
|
var (houseScore, houseScoreBig) = calculateScore(game.houseCards);
|
||
|
// calculate player score
|
||
|
var (playerScore, playerScoreBig) = calculateScore(game.playerCards);
|
||
|
|
||
|
GameStatus(houseScore, houseScoreBig, playerScore, playerScoreBig);
|
||
|
|
||
|
if (houseScoreBig == BLACKJACK || houseScore == BLACKJACK) {
|
||
|
if (playerScore == BLACKJACK || playerScoreBig == BLACKJACK) {
|
||
|
// TIE
|
||
|
if (!msg.sender.send(game.bet)) throw; // return bet to the player
|
||
|
games[msg.sender].state = GameState.Tie; // finish the game
|
||
|
return;
|
||
|
} else {
|
||
|
// HOUSE WON
|
||
|
games[msg.sender].state = GameState.House; // simply finish the game
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
if (playerScore == BLACKJACK || playerScoreBig == BLACKJACK) {
|
||
|
// PLAYER WON
|
||
|
if (game.playerCards.length == 2 && (Deck.isTen(game.playerCards[0]) || Deck.isTen(game.playerCards[1]))) {
|
||
|
// Natural blackjack => return x2.5
|
||
|
if (!msg.sender.send((game.bet * 5) / 2)) throw; // send prize to the player
|
||
|
} else {
|
||
|
// Usual blackjack => return x2
|
||
|
if (!msg.sender.send(game.bet * 2)) throw; // send prize to the player
|
||
|
}
|
||
|
games[msg.sender].state = GameState.Player; // finish the game
|
||
|
return;
|
||
|
} else {
|
||
|
|
||
|
if (playerScore > BLACKJACK) {
|
||
|
// BUST, HOUSE WON
|
||
|
Log(1);
|
||
|
games[msg.sender].state = GameState.House; // finish the game
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!finishGame) {
|
||
|
return; // continue the game
|
||
|
}
|
||
|
|
||
|
// недобор
|
||
|
uint8 playerShortage = 0;
|
||
|
uint8 houseShortage = 0;
|
||
|
|
||
|
// player decided to finish the game
|
||
|
if (playerScoreBig > BLACKJACK) {
|
||
|
if (playerScore > BLACKJACK) {
|
||
|
// HOUSE WON
|
||
|
games[msg.sender].state = GameState.House; // simply finish the game
|
||
|
return;
|
||
|
} else {
|
||
|
playerShortage = BLACKJACK - playerScore;
|
||
|
}
|
||
|
} else {
|
||
|
playerShortage = BLACKJACK - playerScoreBig;
|
||
|
}
|
||
|
|
||
|
if (houseScoreBig > BLACKJACK) {
|
||
|
if (houseScore > BLACKJACK) {
|
||
|
// PLAYER WON
|
||
|
if (!msg.sender.send(game.bet * 2)) throw; // send prize to the player
|
||
|
games[msg.sender].state = GameState.Player;
|
||
|
return;
|
||
|
} else {
|
||
|
houseShortage = BLACKJACK - houseScore;
|
||
|
}
|
||
|
} else {
|
||
|
houseShortage = BLACKJACK - houseScoreBig;
|
||
|
}
|
||
|
|
||
|
// ?????????????????????? почему игра заканчивается?
|
||
|
if (houseShortage == playerShortage) {
|
||
|
// TIE
|
||
|
if (!msg.sender.send(game.bet)) throw; // return bet to the player
|
||
|
games[msg.sender].state = GameState.Tie;
|
||
|
} else if (houseShortage > playerShortage) {
|
||
|
// PLAYER WON
|
||
|
if (!msg.sender.send(game.bet * 2)) throw; // send prize to the player
|
||
|
games[msg.sender].state = GameState.Player;
|
||
|
} else {
|
||
|
games[msg.sender].state = GameState.House;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function calculateScore(uint8[] cards) private constant returns (uint8, uint8) {
|
||
|
uint8 score = 0;
|
||
|
uint8 scoreBig = 0; // in case of Ace there could be 2 different scores
|
||
|
bool bigAceUsed = false;
|
||
|
for (uint i = 0; i < cards.length; ++i) {
|
||
|
uint8 card = cards[i];
|
||
|
if (Deck.isAce(card) && !bigAceUsed) { // doesn't make sense to use the second Ace as 11, because it leads to the losing
|
||
|
scoreBig += Deck.valueOf(card, true);
|
||
|
bigAceUsed = true;
|
||
|
} else {
|
||
|
scoreBig += Deck.valueOf(card, false);
|
||
|
}
|
||
|
score += Deck.valueOf(card, false);
|
||
|
}
|
||
|
return (score, scoreBig);
|
||
|
}
|
||
|
|
||
|
function getPlayerCard(uint8 id) public gameIsGoingOn constant returns(uint8) {
|
||
|
if (id < 0 || id > games[msg.sender].playerCards.length) {
|
||
|
throw;
|
||
|
}
|
||
|
return games[msg.sender].playerCards[id];
|
||
|
}
|
||
|
|
||
|
function getHouseCard(uint8 id) public gameIsGoingOn constant returns(uint8) {
|
||
|
if (id < 0 || id > games[msg.sender].houseCards.length) {
|
||
|
throw;
|
||
|
}
|
||
|
return games[msg.sender].houseCards[id];
|
||
|
}
|
||
|
|
||
|
function getPlayerCardsNumber() public gameIsGoingOn constant returns(uint) {
|
||
|
return games[msg.sender].playerCards.length;
|
||
|
}
|
||
|
|
||
|
function getHouseCardsNumber() public gameIsGoingOn constant returns(uint) {
|
||
|
return games[msg.sender].houseCards.length;
|
||
|
}
|
||
|
|
||
|
function getGameState() public constant returns (uint8) {
|
||
|
if (games[msg.sender].player == 0) {
|
||
|
throw; // game doesn't exist
|
||
|
}
|
||
|
|
||
|
Game game = games[msg.sender];
|
||
|
|
||
|
if (game.state == GameState.Player) {
|
||
|
return 1;
|
||
|
}
|
||
|
if (game.state == GameState.House) {
|
||
|
return 2;
|
||
|
}
|
||
|
if (game.state == GameState.Tie) {
|
||
|
return 3;
|
||
|
}
|
||
|
|
||
|
return 0; // the game is still going on
|
||
|
}
|
||
|
|
||
|
}
|