Contract 0xee44B11148486c05112d6418b987D8B8C2632E8f

 

Contract Overview

Balance:
0.0431 Ether

EtherValue:
$5.38 (@ $124.72/ETH)

Token:
TxHash Block Age From To Value [TxFee]
0x1469a8236270117e79afe9a5acab81237bb04a2d0949aba52c9cea715b3b4ee4700506444 days 11 hrs ago0x95af953bbe9125e706eae7ffe22e85cb7ec4523f  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0.001 Ether0.000195832
0x5b278291b6f9c224109fbe4532f86b19acfe60843fa623162ae156f01e164ace673077490 days 5 hrs ago0xdc75db944ccdb5bbe62c180affe04518cb2eb59f  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0 Ether0.0001785852
0xb3f6d8c54e75234acf5a0a268ffe77fc43b8820099a551c41359644772a43a1a673066190 days 6 hrs ago0xdc75db944ccdb5bbe62c180affe04518cb2eb59f  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0.001 Ether0.0007049952
0xaf9d985b1e0d17ea38a4d68de44ea43dfb8a1a9ea86abe951a1cd58b310bf1a26299364160 days 21 hrs ago0x1e06d230fd7e6f6630b4c1b7f5e8594510b42595  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0 Ether0.000198428
0xfb5b4ec830f6013c1bdc7a0c821a7bad327cd23426673eb33dd7557cb611345f6299349160 days 21 hrs ago0x1e06d230fd7e6f6630b4c1b7f5e8594510b42595  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0.001 Ether0.000783328
0x4ad080a427214475ca986fa5b2f817108d9bb39b65f8079b171791a9e68b7c125674121267 days 19 hrs ago0x562bb56de196bcd30dac3835055316078733fb83  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0.005 Ether0.001288818
0x406e27da06c568ee9ffe5371656f3c80e52baf21569c20e2994abb12342eb9885492259299 days 16 hrs ago0x4c1576a167ace1109ff14a3517e8514ef2dd6551  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0 Ether0.00002357
0xfbacf396266dc55afd688802c776cc18b378905d43e9a4c0cb27bb214a2272a05492225299 days 16 hrs ago0x4c1576a167ace1109ff14a3517e8514ef2dd6551  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0 Ether0.00002357
0x22dda53cd72b217307c2df41871417f0d6a011244622d42ef77da94b63e92f115492199299 days 17 hrs ago0x4c1576a167ace1109ff14a3517e8514ef2dd6551  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0 Ether0.000059048
0x6475874c7672ae279319e7647b0874054fc582423ee24ee767350f51b92c338d5443302308 days 1 hr ago0x4c1576a167ace1109ff14a3517e8514ef2dd6551  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0.005 Ether0.000180832
0xf59577e4ce6c4708b1f77ffe2bbfef650aead29973354d53630a1ae97370307a5443292308 days 1 hr ago0x4c1576a167ace1109ff14a3517e8514ef2dd6551  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0.005 Ether0.000143202
0x6bbeda0efab2d007dfbdb21076e20e52c71ee24313614c14f6f3e9cc703b7a175415531312 days 18 hrs ago0x163129af165b621c128c64517ee820dd2b7e638d  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0.005 Ether0.000195832
0x9b07c6f0c2fa0070c81aec1589d16583f9c913425e4545e6cc16583d633a48555329942326 days 23 hrs ago0x2c3d95efc61c62cac64f39794ffa8f11d88224d1  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0 Ether0.000054
0x24c8125f5a2a10fc25de0408f791e67455f2bb27515bd0f710c204af47b470185329937326 days 23 hrs ago0x2c3d95efc61c62cac64f39794ffa8f11d88224d1  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0 Ether0.00015092
0x7671c6268b9c7e0b86004014d69612fab45b28c9cb94e0df478cca65134cbe955329890327 days 3 mins ago0x2968e2655df359642672e273d7cab88a4aad395f  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0 Ether0.000165844
0x850af1f89c02e82c219ef70af21cdd70910f7033b53331e2fcda3066666e908f5329885327 days 6 mins ago0x2968e2655df359642672e273d7cab88a4aad395f  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0 Ether0.000145604
0x0e37c2588f33c0c97b75bc5ce9c4b3fa8c3e3f872e76eafbc0fea1d4a0ebee935329878327 days 8 mins ago0x2c3d95efc61c62cac64f39794ffa8f11d88224d1  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0.001 Ether0.000286404
0x3ce8e7792dccdeffab4b94b04713450d0c527f66983ddd5311f739608c847d3e5329863327 days 11 mins ago0x2968e2655df359642672e273d7cab88a4aad395f  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0.001 Ether0.000391664
0xaa40ebc8da5995f0c4cf70232f498f4195c8628737f5c53d689d7a718de101dd5329849327 days 14 mins ago0x2c3d95efc61c62cac64f39794ffa8f11d88224d1  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0 Ether0.000047184
0x76e4fe73e162e948c62a94ff2096e912905afe5ddd6bbc64970a3463e0da02a05329848327 days 15 mins ago0x2c3d95efc61c62cac64f39794ffa8f11d88224d1  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0 Ether0.000099214
0x45a4618280e17b3308c105c5a0a31616e1094804510f71cf68903a7c608f81e35329731327 days 44 mins ago0x2c3d95efc61c62cac64f39794ffa8f11d88224d1  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0.001 Ether0.000102444
0x90dfb4e859af2afa7607db357e46b182c4fde36db4ac44a501b59ad09fce7c9f5329731327 days 44 mins ago0x2c3d95efc61c62cac64f39794ffa8f11d88224d1  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0.001 Ether0.000391664
0x32c48ecb95aa63fcd65c88c757ce2ab8ac09dc45cabe75c29af54557fd3e78385068014371 days 6 hrs ago0xa804a7dd19d0022e2d5dd3f83d69ce5e712f6f1d  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0 Ether0.000049607
0x4387e884e367b098968bdf8e31d7352eebbbceddcbaf8f7e8f4c209afd0216285067917371 days 7 hrs ago0xa804a7dd19d0022e2d5dd3f83d69ce5e712f6f1d  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0.001 Ether0.000195832
0x61ab5b85047284c81fb9568dcf568bc9efaef253208967e804b00104dd0309f44927783395 days 39 mins ago0x15196ab21ffbd8cede42ae4fd88bac6d6714dd32  IN   0xee44b11148486c05112d6418b987d8b8c2632e8f0 Ether0.000166508
[ Download CSV Export 

Latest 12 Internal Transactions Internal Transactions as a result of Contract Execution

Parent TxHash Block Age From To Value
0x5b278291b6f9c224109fbe4532f86b19acfe60843fa623162ae156f01e164ace673077490 days 5 hrs ago0xee44b11148486c05112d6418b987d8b8c2632e8f0xdc75db944ccdb5bbe62c180affe04518cb2eb59f0.001 Ether
0xaf9d985b1e0d17ea38a4d68de44ea43dfb8a1a9ea86abe951a1cd58b310bf1a26299364160 days 21 hrs ago0xee44b11148486c05112d6418b987d8b8c2632e8f0x1e06d230fd7e6f6630b4c1b7f5e8594510b425950.001 Ether
0x24c8125f5a2a10fc25de0408f791e67455f2bb27515bd0f710c204af47b470185329937326 days 23 hrs ago0xee44b11148486c05112d6418b987d8b8c2632e8f0x2c3d95efc61c62cac64f39794ffa8f11d88224d10.0001 Ether
0x7671c6268b9c7e0b86004014d69612fab45b28c9cb94e0df478cca65134cbe955329890327 days 3 mins ago0xee44b11148486c05112d6418b987d8b8c2632e8f0x2968e2655df359642672e273d7cab88a4aad395f0.001882 Ether
0x7671c6268b9c7e0b86004014d69612fab45b28c9cb94e0df478cca65134cbe955329890327 days 3 mins ago0xee44b11148486c05112d6418b987d8b8c2632e8f0x009d0f479772002b6008c2b9c85527d9edfc6fcc0.000018 Ether
0x76e4fe73e162e948c62a94ff2096e912905afe5ddd6bbc64970a3463e0da02a05329848327 days 15 mins ago0xee44b11148486c05112d6418b987d8b8c2632e8f0x2c3d95efc61c62cac64f39794ffa8f11d88224d10.001 Ether
0x32c48ecb95aa63fcd65c88c757ce2ab8ac09dc45cabe75c29af54557fd3e78385068014371 days 6 hrs ago0xee44b11148486c05112d6418b987d8b8c2632e8f0xa804a7dd19d0022e2d5dd3f83d69ce5e712f6f1d0.001 Ether
0x61ab5b85047284c81fb9568dcf568bc9efaef253208967e804b00104dd0309f44927783395 days 39 mins ago0xee44b11148486c05112d6418b987d8b8c2632e8f0x15196ab21ffbd8cede42ae4fd88bac6d6714dd320.0647 Ether
0x61ab5b85047284c81fb9568dcf568bc9efaef253208967e804b00104dd0309f44927783395 days 39 mins ago0xee44b11148486c05112d6418b987d8b8c2632e8f0x009d0f479772002b6008c2b9c85527d9edfc6fcc0.0003 Ether
0x928d3c50f98ed99bb29210f9904b34169265dd71a107019ae0a2ae507f818a394927780395 days 39 mins ago0xee44b11148486c05112d6418b987d8b8c2632e8f0x05f138f70934bfbd1cdafbf7518b99a6b7261bf90.035 Ether
0x46b416e7e9c8a82afac7e115985838d2d9a4696d01d32ef0c71d8089b10c000c4845836410 days 1 hr ago0xee44b11148486c05112d6418b987d8b8c2632e8f0x325653fd5c25ca5a38c72f4c3a9b201d25779ec50.001882 Ether
0x46b416e7e9c8a82afac7e115985838d2d9a4696d01d32ef0c71d8089b10c000c4845836410 days 1 hr ago0xee44b11148486c05112d6418b987d8b8c2632e8f0x009d0f479772002b6008c2b9c85527d9edfc6fcc0.000018 Ether
[ Download CSV Export 
Warning: The compiled contract might be susceptible to ExpExponentCleanup (medium/high-severity), EventStructWrongData (very low-severity), NestedArrayFunctionCallDecoder (medium-severity) Solidity Compiler Bugs.

Contract Source Code Verified (Exact Match)
Contract Name: Spineth
Compiler Version: v0.4.19+commit.c4cbbb05
Optimization Enabled: Yes
Runs (Optimizer):  200


Contract Source Code
pragma solidity ^0.4.19;

contract Spineth
{
    /// The states the game will transition through
    enum State
    {
        WaitingForPlayers, // the game has been created by a player and is waiting for an opponent
        WaitingForReveal, // someone has joined and also placed a bet, we are now waiting for the creator to their reveal bet
        Complete // the outcome of the game is determined and players can withdraw their earnings
    }

    /// All possible event types
    enum Event
    {
        Create,
        Cancel,
        Join,
        Reveal,
        Expire,
        Complete,
        Withdraw,
        StartReveal
    }
    
    // The game state associated with a single game between two players
    struct GameInstance
    {
        // Address for players of this game
        // player1 is always the creator
        address player1;
        address player2;
    
        // How much is being bet this game
        uint betAmountInWei;
    
        // The wheelBet for each player
        // For player1, the bet starts as a hash and is only changed to the real bet once revealed
        uint wheelBetPlayer1;
        uint wheelBetPlayer2;
    
        // The final wheel position after game is complete
        uint wheelResult;
    
        // The time by which the creator of the game must reveal his bet after an opponent joins
        // If the creator does not reveal in time, the opponent can expire the game, causing them to win the maximal amount of their bet
        uint expireTime;

        // Current state of the game    
        State state;

        // Tracks whether each player has withdrawn their earnings yet
        bool withdrawnPlayer1;
        bool withdrawnPlayer2;
    }

    /// How many places there are on the wheel that a bet can be placed
    uint public constant WHEEL_SIZE = 19;
    
    /// What percentage of your opponent's bet a player wins for each place on 
    /// the wheel they are closer to the result than their opponent
    /// i.e. If player1 distance from result = 4 and player2 distance from result = 6
    /// then player1 earns (6-4) x WIN_PERCENT_PER_DISTANCE = 20% of player2's bet
    uint public constant WIN_PERCENT_PER_DISTANCE = 10;

    /// The percentage charged on earnings that are won
    uint public constant FEE_PERCENT = 2;

    /// The minimum amount that can be bet
    uint public minBetWei = 1 finney;
    
    /// The maximum amount that can be bet
    uint public maxBetWei = 10 ether;
    
    /// The amount of time creators have to reavel their bets before
    /// the game can be expired by an opponent
    uint public maxRevealSeconds = 3600 * 24;

    /// The account that will receive fees and can configure min/max bet options
    address public authority;

    /// Counters that tracks how many games have been created by each player
    /// This is used to generate a unique game id per player
    mapping(address => uint) private counterContext;

    /// Context for all created games
    mapping(uint => GameInstance) public gameContext;

    /// List of all currently open gameids
    uint[] public openGames;

    /// Indexes specific to each player
    mapping(address => uint[]) public playerActiveGames;
    mapping(address => uint[]) public playerCompleteGames;    

    /// Event fired when a game's state changes
    event GameEvent(uint indexed gameId, address indexed player, Event indexed eventType);

    /// Create the contract and verify constant configurations make sense
    function Spineth() public
    {
        // Make sure that the maximum possible win distance (WHEEL_SIZE / 2)
        // multiplied by the WIN_PERCENT_PER_DISTANCE is less than 100%
        // If it's not, then a maximally won bet can't be paid out
        require((WHEEL_SIZE / 2) * WIN_PERCENT_PER_DISTANCE < 100);

        authority = msg.sender;
    }
    
    // Change authority
    // Can only be called by authority
    function changeAuthority(address newAuthority) public
    {
        require(msg.sender == authority);

        authority = newAuthority;
    }

    // Change min/max bet amounts
    // Can only be called by authority
    function changeBetLimits(uint minBet, uint maxBet) public
    {
        require(msg.sender == authority);
        require(maxBet >= minBet);

        minBetWei = minBet;
        maxBetWei = maxBet;
    }
    
    // Internal helper function to add elements to an array
    function arrayAdd(uint[] storage array, uint element) private
    {
        array.push(element);
    }

    // Internal helper function to remove element from an array
    function arrayRemove(uint[] storage array, uint element) private
    {
        for(uint i = 0; i < array.length; ++i)
        {
            if(array[i] == element)
            {
                array[i] = array[array.length - 1];
                delete array[array.length - 1];
                --array.length;
                break;
            }
        }
    }

    /// Get next game id to be associated with a player address
    function getNextGameId(address player) public view
        returns (uint)
    {
        uint counter = counterContext[player];

        // Addresses are 160 bits so we can safely shift them up by (256 - 160 = 96 bits)
        // to make room for the counter in the bottom 96 bits
        // This means a single player cannot theoretically create more than 2^96 games
        // which should more than enough for the lifetime of any player.
        uint result = (uint(player) << 96) + counter;

        // Check that we didn't overflow the counter (this will never happen)
        require((result >> 96) == uint(player));

        return result;
    }

    /// Used to calculate the bet hash given a wheel bet and a player secret.
    /// Used by a game creator to calculate their bet bash off chain first.
    /// When bet is revealed, contract will use this function to verify the revealed bet is valid
    function createWheelBetHash(uint gameId, uint wheelBet, uint playerSecret) public pure
        returns (uint)
    {
        require(wheelBet < WHEEL_SIZE);
        return uint(keccak256(gameId, wheelBet, playerSecret));
    }
    
    /// Create and initialize a game instance with the sent bet amount.
    /// The creator will automatically become a participant of the game.
    /// gameId must be the return value of getNextGameId(...) for the sender
    /// wheelPositionHash should be calculated using createWheelBetHash(...)
    function createGame(uint gameId, uint wheelPositionHash) public payable
    {
        // Make sure the player passed the correct value for the game id
        require(getNextGameId(msg.sender) == gameId);

        // Get the game instance and ensure that it doesn't already exist
        GameInstance storage game = gameContext[gameId];
        require(game.betAmountInWei == 0); 
        
        // Must provide non-zero bet
        require(msg.value > 0);
        
        // Restrict betting amount
        // NOTE: Game creation can be disabled by setting min/max bet to 0
        require(msg.value >= minBetWei && msg.value <= maxBetWei);

        // Increment the create game counter for this player
        counterContext[msg.sender] = counterContext[msg.sender] + 1;

        // Update game state
        // The creator becomes player1
        game.state = State.WaitingForPlayers;
        game.betAmountInWei = msg.value;
        game.player1 = msg.sender;
        game.wheelBetPlayer1 = wheelPositionHash;
        
        // This game is now open to others and active for the player
        arrayAdd(openGames, gameId);
        arrayAdd(playerActiveGames[msg.sender], gameId);

        // Fire event for the creation of this game
        GameEvent(gameId, msg.sender, Event.Create);
    }
    
    /// Cancel a game that was created but never had another player join
    /// A creator can use this function if they have been waiting too long for another
    /// player and want to get their bet funds back. NOTE. Once someone joins
    /// the game can no longer be cancelled.
    function cancelGame(uint gameId) public
    {
        // Get the game instance and check that it exists
        GameInstance storage game = gameContext[gameId];
        require(game.betAmountInWei > 0); 

        // Can only cancel if we are still waiting for other participants
        require(game.state == State.WaitingForPlayers);
        
        // Is the sender the creator?
        require(game.player1 == msg.sender);

        // Update game state
        // Mark earnings as already withdrawn since we are returning the bet amount
        game.state = State.Complete;
        game.withdrawnPlayer1 = true;

        // This game is no longer open and no longer active for the player
        arrayRemove(openGames, gameId);
        arrayRemove(playerActiveGames[msg.sender], gameId);

        // Fire event for player canceling this game
        GameEvent(gameId, msg.sender, Event.Cancel);

        // Transfer the player's bet amount back to them
        msg.sender.transfer(game.betAmountInWei);
    }

    /// Join an open game instance
    /// Sender must provide an amount of wei equal to betAmountInWei
    /// After the second player has joined, the creator will have maxRevealSeconds to reveal their bet
    function joinGame(uint gameId, uint wheelBet) public payable
    {
        // Get the game instance and check that it exists
        GameInstance storage game = gameContext[gameId];
        require(game.betAmountInWei > 0); 
        
        // Only allowed to participate while we are waiting for players
        require(game.state == State.WaitingForPlayers);
        
        // Can't join a game that you created
        require(game.player1 != msg.sender);
        
        // Is there space available?
        require(game.player2 == 0);

        // Must pay the amount of the bet to play
        require(msg.value == game.betAmountInWei);

        // Make sure the wheelBet makes sense
        require(wheelBet < WHEEL_SIZE);

        // Update game state
        // The sender becomes player2
        game.state = State.WaitingForReveal;
        game.player2 = msg.sender;
        game.wheelBetPlayer2 = wheelBet;
        game.expireTime = now + maxRevealSeconds; // After expireTime the game can be expired

        // This game is no longer open, and is now active for the joiner
        arrayRemove(openGames, gameId);
        arrayAdd(playerActiveGames[msg.sender], gameId);

        // Fire event for player joining this game
        GameEvent(gameId, msg.sender, Event.Join);

        // Fire event for creator, letting them know they need to reveal their bet now
        GameEvent(gameId, game.player1, Event.StartReveal);
    }
    
    /// This can be called by the joining player to force the game to end once the expire
    /// time has been reached. This is a safety measure to ensure the game can be completed
    /// in case where the creator decides to not to reveal their bet. In this case, the creator
    /// will lose the maximal amount of their bet
    function expireGame(uint gameId) public
    {
        // Get the game instance and check that it exists
        GameInstance storage game = gameContext[gameId];
        require(game.betAmountInWei > 0); 

        // Only expire from the WaitingForReveal state
        require(game.state == State.WaitingForReveal);
        
        // Has enough time passed to perform this action?
        require(now > game.expireTime);
        
        // Can only expire the game if you are the second player
        require(msg.sender == game.player2);

        // Player1 (creator) did not reveal bet in time
        // Complete the game in favor of player2
        game.wheelResult = game.wheelBetPlayer2;
        game.wheelBetPlayer1 = (game.wheelBetPlayer2 + (WHEEL_SIZE / 2)) % WHEEL_SIZE;
        
        // This game is complete, the withdrawEarnings flow can now be invoked
        game.state = State.Complete;

        // Fire an event for the player forcing this game to end
        GameEvent(gameId, game.player1, Event.Expire);
        GameEvent(gameId, game.player2, Event.Expire);
    }
    
    /// Once a player has joined the game, the creator must reveal their bet
    /// by providing the same playerSecret that was passed to createGame(...)
    function revealBet(uint gameId, uint playerSecret) public
    {
        // Get the game instance and check that it exists
        GameInstance storage game = gameContext[gameId];
        require(game.betAmountInWei > 0); 

        // We can only reveal bets during the revealing bets state
        require(game.state == State.WaitingForReveal);

        // Only the creator does this
        require(game.player1 == msg.sender);

        uint i; // Loop counter used below

        // Find the wheelBet the player made by enumerating the hash
        // possibilities. It is done this way so the player only has to
        // remember their secret in order to revel the bet
        for(i = 0; i < WHEEL_SIZE; ++i)
        {
            // Find the bet that was provided in createGame(...)
            if(createWheelBetHash(gameId, i, playerSecret) == game.wheelBetPlayer1)
            {
                // Update the bet to the revealed value
                game.wheelBetPlayer1 = i;
                break;
            }
        }
        
        // Make sure we successfully revealed the bet, otherwise
        // the playerSecret was invalid
        require(i < WHEEL_SIZE);
        
        // Fire an event for the revealing of the bet
        GameEvent(gameId, msg.sender, Event.Reveal);

        // Use the revealed bets to calculate the wheelResult
        // NOTE: Neither player knew the unrevealed state of both bets when making their
        // bet, so the combination can be used to generate a random number neither player could anticipate.
        // This algorithm was tested for good outcome distribution for arbitrary hash values
        uint256 hashResult = uint256(keccak256(gameId, now, game.wheelBetPlayer1, game.wheelBetPlayer2));
        uint32 randomSeed = uint32(hashResult >> 0)
                          ^ uint32(hashResult >> 32)
                          ^ uint32(hashResult >> 64)
                          ^ uint32(hashResult >> 96)
                          ^ uint32(hashResult >> 128)
                          ^ uint32(hashResult >> 160)
                          ^ uint32(hashResult >> 192)
                          ^ uint32(hashResult >> 224);

        uint32 randomNumber = randomSeed;
        uint32 randMax = 0xFFFFFFFF; // We use the whole 32 bit range

        // Generate random numbers until we get a value in the unbiased range (see below)
        do
        {
            randomNumber ^= (randomNumber >> 11);
            randomNumber ^= (randomNumber << 7) & 0x9D2C5680;
            randomNumber ^= (randomNumber << 15) & 0xEFC60000;
            randomNumber ^= (randomNumber >> 18);
        }
        // Since WHEEL_SIZE is not divisible by randMax, using modulo below will introduce bias for
        // numbers at the end of the randMax range. To remedy this, we discard these out of range numbers
        // and generate additional numbers until we are in the largest range divisble by WHEEL_SIZE.
        // This range will ensure we do not introduce any modulo bias
        while(randomNumber >= (randMax - (randMax % WHEEL_SIZE)));

        // Update game state        
        game.wheelResult = randomNumber % WHEEL_SIZE;
        game.state = State.Complete;
        
        // Fire an event for the completion of the game
        GameEvent(gameId, game.player1, Event.Complete);
        GameEvent(gameId, game.player2, Event.Complete);
    }

    /// A utility function to get the minimum distance between two selections
    /// on a wheel of WHEEL_SIZE wrapping around at 0
    function getWheelDistance(uint value1, uint value2) private pure
        returns (uint)
    {
        // Make sure the values are within range
        require(value1 < WHEEL_SIZE && value2 < WHEEL_SIZE);

        // Calculate the distance of value1 with respect to value2
        uint dist1 = (WHEEL_SIZE + value1 - value2) % WHEEL_SIZE;
        
        // Calculate the distance going the other way around the wheel
        uint dist2 = WHEEL_SIZE - dist1;

        // Whichever distance is shorter is the wheel distance
        return (dist1 < dist2) ? dist1 : dist2;
    }

    /// Once the game is complete, use this function to get the results of
    /// the game. Returns:
    /// - the amount of wei charged for the fee
    /// - the amount of wei to be paid out to player1
    /// - the amount of wei to be paid out to player2
    /// The sum of all the return values is exactly equal to the contributions
    /// of both player bets. i.e. 
    ///     feeWei + weiPlayer1 + weiPlayer2 = 2 * betAmountInWei
    function calculateEarnings(uint gameId) public view
        returns (uint feeWei, uint weiPlayer1, uint weiPlayer2)
    {
        // Get the game instance and check that it exists
        GameInstance storage game = gameContext[gameId];
        require(game.betAmountInWei > 0); 

        // It doesn't make sense to call this function when the game isn't complete
        require(game.state == State.Complete);
        
        uint distancePlayer1 = getWheelDistance(game.wheelBetPlayer1, game.wheelResult);
        uint distancePlayer2 = getWheelDistance(game.wheelBetPlayer2, game.wheelResult);

        // Outcome if there is a tie
        feeWei = 0;
        weiPlayer1 = game.betAmountInWei;
        weiPlayer2 = game.betAmountInWei;

        uint winDist = 0;
        uint winWei = 0;
        
        // Player one was closer, so they won
        if(distancePlayer1 < distancePlayer2)
        {
            winDist = distancePlayer2 - distancePlayer1;
            winWei = game.betAmountInWei * (winDist * WIN_PERCENT_PER_DISTANCE) / 100;

            feeWei = winWei * FEE_PERCENT / 100;
            weiPlayer1 += winWei - feeWei;
            weiPlayer2 -= winWei;
        }
        // Player two was closer, so they won
        else if(distancePlayer2 < distancePlayer1)
        {
            winDist = distancePlayer1 - distancePlayer2;
            winWei = game.betAmountInWei * (winDist * WIN_PERCENT_PER_DISTANCE) / 100;

            feeWei = winWei * FEE_PERCENT / 100;
            weiPlayer2 += winWei - feeWei;
            weiPlayer1 -= winWei;
        }
        // Same distance, so it was a tie (see above)
    }
    
    /// Once the game is complete, each player can withdraw their earnings
    /// A fee is charged on winnings only and provided to the contract authority
    function withdrawEarnings(uint gameId) public
    {
        // Get the game instance and check that it exists
        GameInstance storage game = gameContext[gameId];
        require(game.betAmountInWei > 0); 

        require(game.state == State.Complete);
        
        var (feeWei, weiPlayer1, weiPlayer2) = calculateEarnings(gameId);

        bool payFee = false;
        uint withdrawAmount = 0;

        if(game.player1 == msg.sender)
        {
            // Can't have already withrawn
            require(game.withdrawnPlayer1 == false);
            
            game.withdrawnPlayer1 = true; // They can't withdraw again
            
            // If player1 was the winner, they will pay the fee
            if(weiPlayer1 > weiPlayer2)
            {
                payFee = true;
            }
            
            withdrawAmount = weiPlayer1;
        }
        else if(game.player2 == msg.sender)
        {
            // Can't have already withrawn
            require(game.withdrawnPlayer2 == false);
            
            game.withdrawnPlayer2 = true;

            // If player2 was the winner, they will pay the fee
            if(weiPlayer2 > weiPlayer1)
            {
                payFee = true;
            }
            
            withdrawAmount = weiPlayer2;
        }
        else
        {
            // The sender isn't a participant
            revert();
        }

        // This game is no longer active for this player, and now moved to complete for this player
        arrayRemove(playerActiveGames[msg.sender], gameId);
        arrayAdd(playerCompleteGames[msg.sender], gameId);

        // Fire an event for the withdrawing of funds
        GameEvent(gameId, msg.sender, Event.Withdraw);

        // Pay the fee, if necessary
        if(payFee == true)
        {
            authority.transfer(feeWei);
        }
    
        // Transfer sender their outcome
        msg.sender.transfer(withdrawAmount);
    }
}

Contract ABI
[{"constant":false,"inputs":[{"name":"newAuthority","type":"address"}],"name":"changeAuthority","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"minBetWei","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"WHEEL_SIZE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"gameId","type":"uint256"}],"name":"calculateEarnings","outputs":[{"name":"feeWei","type":"uint256"},{"name":"weiPlayer1","type":"uint256"},{"name":"weiPlayer2","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"gameContext","outputs":[{"name":"player1","type":"address"},{"name":"player2","type":"address"},{"name":"betAmountInWei","type":"uint256"},{"name":"wheelBetPlayer1","type":"uint256"},{"name":"wheelBetPlayer2","type":"uint256"},{"name":"wheelResult","type":"uint256"},{"name":"expireTime","type":"uint256"},{"name":"state","type":"uint8"},{"name":"withdrawnPlayer1","type":"bool"},{"name":"withdrawnPlayer2","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"maxRevealSeconds","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"minBet","type":"uint256"},{"name":"maxBet","type":"uint256"}],"name":"changeBetLimits","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"player","type":"address"}],"name":"getNextGameId","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"gameId","type":"uint256"},{"name":"wheelPositionHash","type":"uint256"}],"name":"createGame","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"gameId","type":"uint256"}],"name":"expireGame","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"gameId","type":"uint256"}],"name":"cancelGame","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"gameId","type":"uint256"}],"name":"withdrawEarnings","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"WIN_PERCENT_PER_DISTANCE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"uint256"}],"name":"playerCompleteGames","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"gameId","type":"uint256"},{"name":"playerSecret","type":"uint256"}],"name":"revealBet","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"gameId","type":"uint256"},{"name":"wheelBet","type":"uint256"}],"name":"joinGame","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"uint256"}],"name":"playerActiveGames","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FEE_PERCENT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"gameId","type":"uint256"},{"name":"wheelBet","type":"uint256"},{"name":"playerSecret","type":"uint256"}],"name":"createWheelBetHash","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"openGames","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"maxBetWei","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"gameId","type":"uint256"},{"indexed":true,"name":"player","type":"address"},{"indexed":true,"name":"eventType","type":"uint8"}],"name":"GameEvent","type":"event"}]

Contract Creation Code
606060405266038d7ea4c68000600055678ac7230489e8000060015562015180600255341561002d57600080fd5b60038054600160a060020a03191633600160a060020a031617905561115a806100576000396000f3006060604052600436106101275763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663116877cc811461012c5780632d3e20651461014d578063379930f7146101725780633c3efdc9146101855780633e33c37b146101bf5780634cc0a885146102625780635930a295146102755780635d3b2f371461028e57806360104cef146102ad57806361e4a23a146102bb57806369958ab9146102d15780636e70096e146102e7578063779ce152146102fd5780639344b0d614610310578063abfb589b14610332578063b60b9fcb1461034b578063bf7e214f14610359578063d2a6629c14610388578063eaf98d23146103aa578063f59f5e65146103bd578063fd22a6cf146103d9578063fe9ccc1e146103ef575b600080fd5b341561013757600080fd5b61014b600160a060020a0360043516610402565b005b341561015857600080fd5b61016061044c565b60405190815260200160405180910390f35b341561017d57600080fd5b610160610452565b341561019057600080fd5b61019b600435610457565b60405180848152602001838152602001828152602001935050505060405180910390f35b34156101ca57600080fd5b6101d5600435610553565b604051808b600160a060020a0316600160a060020a031681526020018a600160a060020a0316600160a060020a0316815260200189815260200188815260200187815260200186815260200185815260200184600281111561023357fe5b60ff16815292151560208401525015156040808301919091526060909101985096505050505050505180910390f35b341561026d57600080fd5b6101606105ba565b341561028057600080fd5b61014b6004356024356105c0565b341561029957600080fd5b610160600160a060020a03600435166105f3565b61014b600435602435610636565b34156102c657600080fd5b61014b600435610743565b34156102dc57600080fd5b61014b60043561082e565b34156102f257600080fd5b61014b600435610930565b341561030857600080fd5b610160610b1a565b341561031b57600080fd5b610160600160a060020a0360043516602435610b1f565b341561033d57600080fd5b61014b600435602435610b4d565b61014b600435602435610dce565b341561036457600080fd5b61036c610f2d565b604051600160a060020a03909116815260200160405180910390f35b341561039357600080fd5b610160600160a060020a0360043516602435610f3c565b34156103b557600080fd5b610160610f57565b34156103c857600080fd5b610160600435602435604435610f5c565b34156103e457600080fd5b610160600435610f9a565b34156103fa57600080fd5b610160610fb9565b60035433600160a060020a0390811691161461041d57600080fd5b6003805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0392909216919091179055565b60005481565b601381565b6000818152600560205260408120600281015482918291829081908190819081901161048257600080fd5b6002600786015460ff16600281111561049757fe5b146104a157600080fd5b6104b385600301548660050154610fbf565b93506104c785600401548660050154610fbf565b600286015460009950975087965092508791508190508284101561051457600285015484840392506064908302600a02049050606460028202049750878103870196508086039550610547565b8383101561054757600285015483850392506064908302600a020490506064600282020497508781038601955080870396505b50505050509193909250565b600560208190526000918252604090912080546001820154600283015460038401546004850154958501546006860154600790960154600160a060020a039586169794909516959294919392909160ff80821691610100810482169162010000909104168a565b60025481565b60035433600160a060020a039081169116146105db57600080fd5b818110156105e857600080fd5b600091909155600155565b600160a060020a03811660008181526004602052604081205490916c010000000000000000000000008082028301919082041461062f57600080fd5b9392505050565b600082610642336105f3565b1461064c57600080fd5b50600082815260056020526040902060028101541561066a57600080fd5b6000341161067757600080fd5b600054341015801561068b57506001543411155b151561069657600080fd5b33600160a060020a031660008181526004602052604090208054600101905560078201805460ff19169055346002830155815473ffffffffffffffffffffffffffffffffffffffff1916178155600381018290556106f5600684611007565b600160a060020a03331660009081526007602052604090206107179084611007565b600033600160a060020a03168460008051602061110f83398151915260405160405180910390a4505050565b600081815260056020526040812060028101549091901161076357600080fd5b6001600782015460ff16600281111561077857fe5b1461078257600080fd5b6006810154421161079257600080fd5b600181015433600160a060020a039081169116146107af57600080fd5b60048101546005820181905560139060090106600382015560078101805460ff191660021790558054600490600160a060020a03168360008051602061110f83398151915260405160405180910390a460046001820154600160a060020a03168360008051602061110f83398151915260405160405180910390a45050565b600081815260056020526040812060028101549091901161084e57600080fd5b6000600782015460ff16600281111561086357fe5b1461086d57600080fd5b805433600160a060020a0390811691161461088757600080fd5b600781018054600260ff199091161761ff0019166101001790556108ac600683611029565b600160a060020a03331660009081526007602052604090206108ce9083611029565b600133600160a060020a03168360008051602061110f83398151915260405160405180910390a433600160a060020a03166108fc82600201549081150290604051600060405180830381858888f19350505050151561092c57600080fd5b5050565b60008181526005602052604081206002810154909190819081908190819081901161095a57600080fd5b6002600787015460ff16600281111561096f57fe5b1461097957600080fd5b61098287610457565b885492975090955093506000925082915033600160a060020a03908116911614156109e3576007860154610100900460ff16156109be57600080fd5b60078601805461ff001916610100179055828411156109dc57600191505b5082610a37565b600186015433600160a060020a039081169116141561012757600786015462010000900460ff1615610a1457600080fd5b60078601805462ff000019166201000017905583831115610a3457600191505b50815b600160a060020a0333166000908152600760205260409020610a599088611029565b600160a060020a0333166000908152600860205260409020610a7b9088611007565b600633600160a060020a03168860008051602061110f83398151915260405160405180910390a460018215151415610ae057600354600160a060020a031685156108fc0286604051600060405180830381858888f193505050501515610ae057600080fd5b600160a060020a03331681156108fc0282604051600060405180830381858888f193505050501515610b1157600080fd5b50505050505050565b600a81565b600860205281600052604060002081815481101515610b3a57fe5b6000918252602090912001549150829050565b600082815260056020526040812060028101549091908190819081908190819011610b7757600080fd5b6001600787015460ff166002811115610b8c57fe5b14610b9657600080fd5b855433600160a060020a03908116911614610bb057600080fd5b600094505b6013851015610beb578560030154610bce89878a610f5c565b1415610be05760038601859055610beb565b846001019450610bb5565b60138510610bf857600080fd5b600333600160a060020a03168960008051602061110f83398151915260405160405180910390a487428760030154886004015460405180858152602001848152602001838152602001828152602001945050505050604051908190039020935050505064010000000081048118680100000000000000008204186c010000000000000000000000008204187001000000000000000000000000000000008204187401000000000000000000000000000000000000000082041878010000000000000000000000000000000000000000000000008204187c01000000000000000000000000000000000000000000000000000000008204188063ffffffff5b6204000061080063ffffffff84811691909104909318808416608002639d2c568016188084166180000263efc6000016188084169190910418916013908216068163ffffffff16038263ffffffff16101515610d5157610cf6565b601363ffffffff83160660058781019190915560078701805460ff191660021790558654600160a060020a03168960008051602061110f83398151915260405160405180910390a460056001870154600160a060020a03168960008051602061110f83398151915260405160405180910390a45050505050505050565b6000828152600560205260408120600281015490919011610dee57600080fd5b6000600782015460ff166002811115610e0357fe5b14610e0d57600080fd5b805433600160a060020a0390811691161415610e2857600080fd5b6001810154600160a060020a031615610e4057600080fd5b60028101543414610e5057600080fd5b60138210610e5d57600080fd5b600781018054600160ff1990911681179091558101805473ffffffffffffffffffffffffffffffffffffffff191633600160a060020a0316179055600481018290556002544201600680830191909155610eb79084611029565b600160a060020a0333166000908152600760205260409020610ed99084611007565b600233600160a060020a03168460008051602061110f83398151915260405160405180910390a460078154600160a060020a03168460008051602061110f83398151915260405160405180910390a4505050565b600354600160a060020a031681565b600760205281600052604060002081815481101515610b3a57fe5b600281565b600060138310610f6b57600080fd5b838383604051808481526020018381526020018281526020019350505050604051908190039020949350505050565b6006805482908110610fa857fe5b600091825260209091200154905081565b60015481565b6000806000601385108015610fd45750601384105b1515610fdf57600080fd5b60138486038101069150816013039050808210610ffc5780610ffe565b815b95945050505050565b815482906001810161101983826110ce565b5060009182526020909120015550565b60005b82548110156110c95781838281548110151561104457fe5b90600052602060002090015414156110c15782548390600019810190811061106857fe5b906000526020600020900154838281548110151561108257fe5b6000918252602090912001558254839060001981019081106110a057fe5b60009182526020822001558254600019016110bb84826110ce565b506110c9565b60010161102c565b505050565b8154818355818115116110c9576000838152602090206110c991810190830161110b91905b8082111561110757600081556001016110f3565b5090565b905600afff33f4f313ba77ae394cf099251eb678a6e8295e7b173860e24e7da1fa6ef3a165627a7a72305820ade44effe03dd9be4665049aae02b3ea4c3fdc19f6ffa807177da58af47400110029


   Swarm Source:
bzzr://ade44effe03dd9be4665049aae02b3ea4c3fdc19f6ffa807177da58af4740011
Block Age transaction Difficulty GasUsed Reward
Block Age Uncle Number Difficulty GasUsed Reward
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.