智能合约 Solidity 梭哈

pragma solidity ^0.4.21;

/**
 * https://zh.pokerstrategy.com/strategy/various-poker/566/
 * 开局
 * 1. 各下底注
 * 2. 发底牌
 *
 * 每个回合 x4
 * 1、发牌,判断该谁说话
 * 2、轮流行动(下注、加注、过、丢牌),判断是否行动结束
 * 3、判断游戏是否结束,结束则开牌结账,没结束则继续下一回合
 * 
 */

contract ShowHand {
    address private constant dealer = 0x124a983ce7472AB6507347C30984A358Adb7c2E1;
 
    uint private constant timeToReact = 3 minutes;
    uint private gameValidUntil;

    bytes32 seed;

    uint public bringInBet = 1 wei;
    uint public minimalBet = 1 wei;
 
    address public activePlayer;
    address public hostPlayer;
    address public guestPlayer;
    address public winner;
 
    mapping(address=>uint8[5]) public cards;
    mapping(address=>uint) public totalBalance;
    mapping(address=>uint) public roundBalance;
    mapping(address=>bool) public isPlayerActioned;
 
    uint8 public round;
    bool isGameFinished;
 
    event OnGameCreated();

    event OnPlayerJoin(address player);
    event OnPlayerBet(address player, uint amount);
    event OnPlayerCheck(address player);
    event OnPlayerFold(address player);
    event OnPlayerWin(address winner);

    event OnCardDeal(address player, uint8 card);
    event OnPlayerTurn(address player);
    event OnNextRound(uint8 round);

    event OnPayoutSuccess(address player, uint amount);
    event OnRefundSuccess(address player, uint amount);
 
    event OnLog(address msg);
 
    function ShowHand(uint _bringInBet, uint _minimalBet) public {
        bringInBet = _bringInBet;
        minimalBet = _minimalBet;
 
        hostPlayer = msg.sender;

        gameValidUntil = now + timeToReact;
 
        seed = block.blockhash(block.number - 1);
    }
 
    function Join() public {
        assert(guestPlayer == address(0));
 
        guestPlayer = msg.sender;
        emit OnPlayerJoin(msg.sender);
 
        gameValidUntil = now + timeToReact;
    }

    // 下底注
    function BringIn() public payable {
        assert(round == 0);
        assert(totalBalance[msg.sender] == 0); // 玩家之前没有下过注
        require(msg.value == bringInBet); // 筹码等于底注大小
 
        totalBalance[msg.sender] = msg.value;
        isPlayerActioned[msg.sender] = true;
 
        // 如果所有玩家都下完底注
        if (isAllPlayerActioned()) {
            // 发底牌
            cards[hostPlayer][0] = DealCard();
            cards[guestPlayer][0] = DealCard();

            // 发明牌
            cards[hostPlayer][1] = DealCard();
            emit OnCardDeal(hostPlayer, cards[hostPlayer][1]);

            cards[guestPlayer][1] = DealCard();
            emit OnCardDeal(guestPlayer, cards[guestPlayer][1]);

            nextRound();
        }

        gameValidUntil = now + timeToReact;
    }

    // 下注
    function Bet() public payable {
        assert(round > 0 && !isGameFinished);
        require(msg.sender == activePlayer);
        require(msg.value >= minimalBet);
 
        // 当前回合,下注后的总和要大于对手
        if (activePlayer == hostPlayer) {
            require(roundBalance[hostPlayer] + msg.value >= roundBalance[guestPlayer]);
        } else {
            require(roundBalance[guestPlayer] + msg.value >= roundBalance[hostPlayer]);
        }
 
        isPlayerActioned[msg.sender] = true;
        roundBalance[msg.sender] += msg.value;
        totalBalance[msg.sender] += msg.value;
        emit OnPlayerBet(msg.sender, msg.value);
 
        nextMove();
        gameValidUntil = now + timeToReact;
    }
 
    // 过牌
    function Check() public {
        assert(round > 0 && !isGameFinished);
        require(msg.sender == activePlayer);
        require(roundBalance[hostPlayer] == roundBalance[guestPlayer]);
 
        isPlayerActioned[msg.sender] = true;
        emit OnPlayerCheck(msg.sender);

        nextMove();
        gameValidUntil = now + timeToReact;
    }

    // 弃牌
    function Fold() public {
        assert(round > 0 && !isGameFinished);
        require(msg.sender == activePlayer);
 
        emit OnPlayerFold(msg.sender);

        nextPlayer();
        setWinner(activePlayer);
    }

    // 发牌
    function DealCard() public returns (uint8) {
        return uint8Rand(52);
    }
 
    function nextMove() private {
        assert(round > 0);

        if (!isAllPlayerActioned() || 
            roundBalance[hostPlayer] != roundBalance[guestPlayer]) {
            nextPlayer();
            return;
        }
 
        // 下注结束,判断是否是最后一轮下注,是则开牌
        if (round == 4) {
            if (CompareCards(0, cards[hostPlayer], cards[guestPlayer]) >= 0) {
                setWinner(hostPlayer);
            } else {
                setWinner(guestPlayer);
            }
            return;
        }
 
        // 不是最后一轮,继续发牌,然后判定谁发言
        cards[hostPlayer][round] = DealCard();
        emit OnCardDeal(hostPlayer, cards[hostPlayer][round]);
 
        cards[guestPlayer][round] = DealCard();
        emit OnCardDeal(guestPlayer, cards[guestPlayer][round]);

        nextRound();
   }

   // 开始下一回合
   function nextRound() private {
       round++;
       resetRoundData();
       emit OnNextRound(round);

       // 根据牌面判定谁发言
       determineActivePlayer();
   }

   // 重置回合数据
   function resetRoundData() private {
       activePlayer = address(0);
       roundBalance[hostPlayer] = 0;
       roundBalance[guestPlayer] = 0;
       isPlayerActioned[hostPlayer] = false;
       isPlayerActioned[guestPlayer] = false;
   }
 
   // 根据牌面判定谁发言
   function determineActivePlayer() public {
       if (CompareCards(1, cards[hostPlayer], cards[guestPlayer]) >= 0) {
           activePlayer = hostPlayer;
       } else {
           activePlayer = guestPlayer;
       }
 
       emit OnPlayerTurn(activePlayer);
   }
 
   // 切换到另外一个玩家行动
   function nextPlayer() private {
       if (activePlayer == hostPlayer) {
            activePlayer = guestPlayer;
       } else {
            activePlayer = hostPlayer;
       }
 
       emit OnPlayerTurn(activePlayer);
   }
 
   function setWinner(address player) private {
       assert(!isGameFinished);
       assert(winner == address(0));
 
       isGameFinished = true;
       winner = player;
       emit OnPlayerWin(winner);
   }
 
   // 判断是否所有玩家都行动过了
   function isAllPlayerActioned() public view returns(bool) {
       return isPlayerActioned[hostPlayer] && isPlayerActioned[guestPlayer];
   }

   //比较卡牌的大小,简化版本
   function CompareCards(uint8 start, uint8[5] cards1, uint8[5] cards2) private pure returns(int) {
       // uint - uint = uint,所以 sum 必须声明为 int
       int sum1 = 0;
       int sum2 = 0;
       for (uint8 i = start; i < 5; i++) {
            sum1 += cards1[i] % 13;
            sum2 += cards2[i] % 13;
       }
 
       return sum1 - sum2;
   }
 
   // 玩家拖延,呼叫裁判判定
   function CallReferee() public {
       require(!isGameFinished);

       if (gameValidUntil < now) {
            if (activePlayer == hostPlayer) {
                setWinner(guestPlayer);
            } else {
                setWinner(hostPlayer);
            }
       }
   }
 
   // 用户提款
   function Withdraw() public payable {
       assert(winner == msg.sender);
       assert(isGameFinished);
 
       uint total = totalBalance[hostPlayer] + totalBalance[guestPlayer];
       uint winnerReward = uint(total * 9 / 10);
 
       uint hostPlayerRefund = totalBalance[hostPlayer];
       uint guestPlayerRefund = totalBalance[guestPlayer];
       totalBalance[hostPlayer] = 0;
       totalBalance[guestPlayer] = 0;

       if (winner.send(winnerReward)) {
           emit OnPayoutSuccess(winner, winnerReward);
 
           // send rest balance to dealer
           uint dealerReward = address(this).balance;
           if (dealer.send(dealerReward)) {
               emit OnPayoutSuccess(dealer, dealerReward);
           }
       } else {
           // refunds money to player
           if (hostPlayer.send(hostPlayerRefund)) {
               emit OnRefundSuccess(hostPlayer, hostPlayerRefund);
           }
 
           if (guestPlayer.send(guestPlayerRefund)) {
               emit OnRefundSuccess(guestPlayer, guestPlayerRefund);
           }
       }
   }
 
   // fallback function
   function() public payable {
       if (msg.value > 0) {
           dealer.transfer(msg.value);
       }
   }
 
   function uint8Rand(uint8 range) public returns(uint8) {
       seed = sha256(seed);
       return uint8(seed) % range;
   }
}