/*
 ____   _______  _______  _______  __   __    __   __  _______  __    _  ___   _  _______  __   __ 
|    | |  _    ||  _    ||       ||  | |  |  |  |_|  ||       ||  |  | ||   | | ||       ||  | |  |
 |   | | | |   || | |   ||_     _||  |_|  |  |       ||   _   ||   |_| ||   |_| ||    ___||  |_|  |
 |   | | | |   || | |   |  |   |  |       |  |       ||  | |  ||       ||      _||   |___ |       |
 |   | | |_|   || |_|   |  |   |  |       |  |       ||  |_|  ||  _    ||     |_ |    ___||_     _|
 |   | |       ||       |  |   |  |   _   |  | ||_|| ||       || | |   ||    _  ||   |___   |   |  
 |___| |_______||_______|  |___|  |__| |__|  |_|   |_||_______||_|  |__||___| |_||_______|  |___|  

                                          
                                           `oyyydssydy-                                             
                                    `/so/`:hyyhhhyyhhhddyooo+/:-`                                   
                                     `:dhhdhyhhyyhhyhhhhhhhhhhyyyso+-`                              
                               `-+oyhhhhhhhhhhhhhhhhhhhhyysssyyyyysoos+/.                           
                           `:oydhhhhhhhhhhhhhhhhhhhhhhyyyyyyysssssssso+/oo/`                        
                        `/ydhhhhhhhhhhhhhhhhhhhhhhhyysssssssssssssssossso++s+`                      
                      -sysyhhhhhhhhhhhhhhhhhhhhhyyssssssysssssssoooosooosso+oy+`                    
                    .sy+ohhhhhhhhhhhhhhhhhhhhyyyyyyyyyyysssssssssssooosoossso+sy:                   
                  `oy++syhhhhhhhhhhhhhhddhhhhhhhhhhhhhhhhhhhyysssssssssssssssoosys`                 
                 :hs+oyhhhhhhhhhhhddddddhysyhhhhhhhhhhhhhhhhhhhhyysssssysssssyssssy`                
                ohosyhhhhhhhhhhhhhddddhhsyhyhhhhhhhhddddddddddddhhhyysssyysssyhyhyhh`  `----.       
              `yhsyhhhhhhdddddddddddhhhyhyshhhhhddhyo+//:::://oshddhhhssshhysyhhhhdds:/-.```-/:`    
             `yhhhhhhhhhhddmdddddhhhhhhhhhhhhddyo/:-------------:+yddhhyshhhhhhhddhy:`.-//+/-..+`   
             yhhhhhhhhddddddddddmdddhhhhhhhhdy+:--:::::-----------:ohddhhhhhhhhddhs:-/oyysssyo-.o   
            +hhhhhhdddhys+/////ydhddddhhdhdds/-:++/::---------------+dmdhhhhhddhho///ss++++++ss.::  
           `dhhhhdddy+/:------/ddyshdhdddddy/-+s+/-::/+++++:--------:sdddhhhhddho/+ooo+++///++y:.o  
    `-::::-+dhhhddh+:---::://:oh+/+dhdhsddd+--s////++:`./++oo/-------+hmdhhhddhdhdho+++oo+////+:`o  
  .:-.`..-/dhhhddy/:-::::::/+os+//odddo+hdd/--:///o/` omo..:hy+------/hmddhdddmddmhddddyssyo///-`o  
 ::`.-:////mhhhdh//------://++++:-/yddo//sd/:--//++  oNmy-.-sNh/-----/hmddddhdddddmmddyy+++s///--/  
::`./ssooysmhhddo/-----/o/.`.:/o+:-:/os+:-:/:--:/s-  dNNNmmmNNNo-----/hmdddddddddddddyo++//////.o`  
+..:s+++++omdhdd+/----:s. :y:-/yyo:----::::-----/o:  sNNNNNNNNNo-----+dmdddddddddmmmmmmy///////:/   
o`-/s/////+dhhdd+/----++  mdo/+dNm+-------------:+s. `dNNNNNNNh/-----sddddddddddddmdyoos///////o    
o`-/o//:://yhhhdo/:---/o` dNNNNNNNy:-------------:/o-``+dNNNmdosoo+-/hmddddddddddmNddo++/////++     
+.-://+syssydhhhh//---:o: .dNNNNNNy:----::::::::::::+++/:+shho/----:ymdddddddddddNsso+++++//o/      
`+.-///+oyyhdhhhds/:---:o+.`/ydmdh+:/++//:::-:---::::////+yo:-----:ymmdddddddddddm////////+/`       
 -/.:+/+oohdhhhhhds//----/++///+oo+/:-------------------://+::--:/hmmdddddddddddmmhs++++/:`         
  :/./s/:ydhdhhhhhds//:--+soosss+:--------------------------//++sdmmmdddddddmmmmmmmh                
   -+.-osy+odddhhhhdho//::---/+:-----------://:::--------------/odmmmdddddddmmmmmmd.                
    `//--:++hyyhdhhhdmhs+///o/--:-:/:-----::-/++/----------------/sdmmddddddmmmmmd.                 
      .//----:+oyddddddmmdhs/--+-/+oo+/:--:+++/+/-------------...-/ohmmddddmmmmmy`                  
        `-:/++++/smmdddddmh/---:::++oo+/--//////:---------:++:--:///+hmmddmmmmd/                    
              ``/hmmdddddd+:------::///:--:///::----------:/ooo+/////sdmmmmmdo`                     
              -dmdmmmdhyhs//:-------------------------::/oss/////////odmmmh/`                       
              d/`dmmmdo/:----::::::::::::::-------::/ooo+++//////////odh+.                          
              . `d-+dmds+/:::---......-----::://+oo+:.``+o///////////s`                             
                 `  /dy-+o++++++o+++++++ooo+++/:.`   -+s+///////////o-                              
                     `.   +/////+oshddddy+-.`  `.-/oyhs/://////////o-                               
                           :+///////+oyddddhhhhhhhhhs+:-/////////++.                                
                            `//////////+oshhdhhhyso/:--:///////++-                                  
                              ./+/////////:::::-....-://////+o/.                                    
                                `:/+/////////:::::///////++/-                                       
                                    .::/++/////////+++/:-`                                          
                                         `.-------.`        

The game starts on Saturday, November 10, 2018 at 5:00:00 PM (GMT)

https://monkey.game
https://discord.gg/3UQ4dNj

DISCLAIMER:

Please note, during your use of this contract, that online gambling is an entertainment vehicle, and that it carries with it a certain degree of financial risk. 
Players should be aware of this risk, and govern themselves accordingly. All users of this contract should exercise responsibility when playing in online casinos, lotteries, and dAPPS. 
CIRCUS FREAKS PRODUCTIONS have undertaken the appropriate steps to inform all those interested in online gaming about the dangers of excess play; that could potentially result from such an activity. 
There is nothing contained herein that constitutes a guarantee of winning, nor is there an intention to induce anyone into violating any local, state or national laws. 
Recognizing that the laws and regulations involving online gaming are different everywhere, readers are advised to check with the laws that exist within their own jurisdiction to ascertain the legality of the activities which are covered.

*/

pragma solidity ^0.4.25;

library SafeMath {

	function mul(uint256 a, uint256 b) internal pure returns (uint256) {
		if (a == 0) {
			return 0;
		}
		uint256 c = a * b;
		require(c / a == b, "the SafeMath multiplication check failed");
		return c;
	}

	function div(uint256 a, uint256 b) internal pure returns (uint256) {
		require(b > 0, "the SafeMath division check failed");
		uint256 c = a / b;
		return c;
	}

	function sub(uint256 a, uint256 b) internal pure returns (uint256) {
		require(b <= a, "the SafeMath subtraction check failed");
		return a - b;
	}

	function add(uint256 a, uint256 b) internal pure returns (uint256) {
		uint256 c = a + b;
		require(c >= a, "the SafeMath addition check failed");
		return c;
	}

	function mod(uint256 a, uint256 b) internal pure returns (uint256) {
		require(b != 0, "the SafeMath modulo check failed");
		return a % b;
	}
}

contract OneHundredthMonkey {

	using SafeMath for uint256;

	///////////
	//STORAGE//
	///////////

	//ADMIN
	uint256 public adminBalance;
	uint256 public foundationBalance;
	address public adminBank;
	address public foundationFund;
	address[] public admins;
	mapping (address => bool) public isAdmin;

	//GLOBAL
	bool public gameActive = false;
	bool public earlyResolveACalled = false;
	bool public earlyResolveBCalled = false;
	uint256 public activationTime = 1541869200; // (GMT): Saturday, November 10, 2018 5:00:00 PM
	uint256 public miniGamesPerRound = 100; 
	uint256 public miniGamesPerCycle = 1000; 
	uint256 public miniGamePotRate = 25; //25%
	uint256 public progressivePotRate = 25; //25%
	uint256 public roundDivRate = 20; //20%
	uint256 public miniGameDivRate = 10; //10%
	uint256 public referralRate = 10; //10%
	uint256 public miniGameAirdropRate = 5; //5%
	uint256 public adminFeeRate = 5; //5%
	uint256 public roundPotRate = 48; //48% of progressive pot 
	uint256 internal precisionFactor = 18; 
	uint256 public seedAreward = 25000000000000000; 
	uint256 public seedBreward = 25000000000000000; 
	mapping (uint256 => bool) public miniGameSeedAawarded;
	mapping (uint256 => bool) public miniGameSeedBawarded;
	
	//RNG
	uint256 internal RNGblockDelay = 1;
	uint256 internal salt = 0; 
	bytes32 internal hashA; 
	bytes32 internal hashB; 

	//MINIGAME TRACKING
	bool public miniGameProcessing;
	uint256 public miniGameCount;
	uint256 public miniGameProcessingBegun;
	mapping (uint256 => bool) public miniGamePrizeClaimed;
	mapping (uint256 => bool) public miniGameAirdropClaimed;
	mapping (uint256 => uint256) public miniGameStartTime;
	mapping (uint256 => uint256) public miniGameEndTime;
	mapping (uint256 => uint256) public miniGameTokens;
	mapping (uint256 => uint256) public miniGameTokensLeft;
	mapping (uint256 => uint256) public miniGameTokensActive;
	mapping (uint256 => uint256) public miniGameTokenRangeMin;
	mapping (uint256 => uint256) public miniGameTokenRangeMax;
	mapping (uint256 => uint256) public miniGamePrizeNumber;
	mapping (uint256 => uint256) public miniGameAirdropNumber;
	mapping (uint256 => uint256) public miniGamePrizePot;
	mapping (uint256 => uint256) public miniGameAirdropPot;
	mapping (uint256 => uint256) public miniGameDivs;
	mapping (uint256 => uint256) public miniGameDivsClaimed;
	mapping (uint256 => address) public miniGamePrizeWinner;
	mapping (uint256 => address) public miniGameAirdropWinner;

	//ROUND TRACKING
	uint256 public roundCount;
	mapping (uint256 => bool) public roundPrizeClaimed;
	mapping (uint256 => bool) public roundPrizeTokenRangeIdentified;
	mapping (uint256 => uint256) public roundStartTime;
	mapping (uint256 => uint256) public roundEndTime;
	mapping (uint256 => uint256) public roundTokens;
	mapping (uint256 => uint256) public roundTokensActive;
	mapping (uint256 => uint256) public roundTokenRangeMin;
	mapping (uint256 => uint256) public roundTokenRangeMax;
	mapping (uint256 => uint256) public roundPrizeNumber;
	mapping (uint256 => uint256) public roundPrizePot;
	mapping (uint256 => uint256) public roundDivs;
	mapping (uint256 => uint256) public roundDivsClaimed;
	mapping (uint256 => uint256) public roundPrizeInMinigame;
	mapping (uint256 => address) public roundPrizeWinner;

	//CYCLE TRACKING
	bool public cycleOver = false;
	bool public cylcePrizeClaimed;
	bool public cyclePrizeTokenRangeIdentified;
	uint256 public totalVolume;
	uint256 public totalBuys;
	uint256 public tokenSupply;
	uint256 public cycleActiveTokens;
	uint256 public cycleCount;
	uint256 public cycleEnded;
	uint256 public cycleProgressivePot;
	uint256 public cyclePrizeWinningNumber;
	uint256 public cyclePrizeInMinigame;
	uint256 public cyclePrizeInRound;
	uint256 public cycleStartTime;
	address public cyclePrizeWinner;

	//TOKEN TRACKING
	uint256 public tokenPrice = 0.001 ether; 
	uint256 public tokenPriceIncrement = 0.0005 ether; 
	uint256 public minTokensPerMiniGame = 10000; //between 1x and 2x this amount of tokens generated each minigame 

	//USER TRACKING PUBLIC
	address[] public uniqueAddress;
	mapping (address => bool) public knownUsers;
	mapping (address => uint256) public userTokens;
	mapping (address => uint256) public userBalance;
	mapping (address => mapping (uint256 => uint256)) public userMiniGameTokens;
	mapping (address => mapping (uint256 => uint256)) public userRoundTokens;
	mapping (address => mapping (uint256 => uint256[])) public userMiniGameTokensMin;
	mapping (address => mapping (uint256 => uint256[])) public userMiniGameTokensMax;

	//USER TRACKING INTERNAL
	mapping (address => bool) internal userCycleChecked;
	mapping (address => uint256) internal userLastMiniGameInteractedWith;
	mapping (address => uint256) internal userLastRoundInteractedWith;
	mapping (address => uint256) internal userLastMiniGameChecked;
	mapping (address => uint256) internal userLastRoundChecked;
	mapping (address => mapping (uint256 => uint256)) internal userShareMiniGame;
	mapping (address => mapping (uint256 => uint256)) internal userDivsMiniGameTotal;
	mapping (address => mapping (uint256 => uint256)) internal userDivsMiniGameClaimed;
	mapping (address => mapping (uint256 => uint256)) internal userDivsMiniGameUnclaimed;
	mapping (address => mapping (uint256 => uint256)) internal userShareRound;
	mapping (address => mapping (uint256 => uint256)) internal userDivsRoundTotal;
	mapping (address => mapping (uint256 => uint256)) internal userDivsRoundClaimed;
	mapping (address => mapping (uint256 => uint256)) internal userDivsRoundUnclaimed;

	
	///////////////
	//CONSTRUCTOR//
	///////////////

	constructor(address _adminBank, address _foundationFund, address _adminTwo, address _adminThree, address _adminFour) public {
		//set dev bank address and admins
		adminBank = _adminBank;
		foundationFund = _foundationFund;
		admins.push(msg.sender);
		isAdmin[msg.sender] = true;
		admins.push(_adminTwo);
		isAdmin[_adminTwo] = true; 
		admins.push(_adminThree);
		isAdmin[_adminThree] = true; 
		admins.push(_adminFour);
		isAdmin[_adminFour] = true; 
	}

	
	/////////////
	//MODIFIERS//
	/////////////

	modifier onlyAdmins() {
		require (isAdmin[msg.sender] == true, "you must be an admin");
		_;
	}

	modifier onlyHumans() { 
	    require (msg.sender == tx.origin, "only approved contracts allowed"); 
	    _; 
	  }

	modifier gameOpen() {
		require (gameActive == true || now >= activationTime, "the game must be open");
	  	if (miniGameProcessing == true) {
	  		require (block.number > miniGameProcessingBegun + RNGblockDelay, "the round is still processing. try again soon");
	  	}
	  	_;
	}
    
    
    //////////
	//EVENTS//
	//////////

	event adminWithdrew(
		uint256 _amount,
		address indexed _caller,
		string _message 
	);

	event cycleStarted(
		address indexed _caller,
		string _message
	);

	event adminAdded(
		address indexed _caller,
		address indexed _newAdmin,
		string _message
	);

	event resolvedEarly(
		address indexed _caller,
		uint256 _pot,
		string _message
	);

	event processingRestarted(
		address indexed _caller,
		string _message
	);

	event contractDestroyed(
		address indexed _caller,
		uint256 _balance,
		string _message
	);

	event userBought(
		address indexed _user,
		uint256 _tokensBought,
		uint256 indexed _miniGameID,
		string _message
	);

	event userReinvested(
		address indexed _user,
		uint256 _amount,
		string _message
	);

	event userWithdrew(
		address indexed _user,
		uint256 _amount,
		string _message
	);

	event processingStarted(
		address indexed _caller,
		uint256 indexed _miniGameID,
		uint256 _blockNumber,
		string _message
	);

	event processingFinished(
		address indexed _caller,
		uint256 indexed _miniGameID,
		uint256 _blockNumber,
		string _message
	);

	event newMinigameStarted(
		uint256 indexed _miniGameID,
		uint256 _newTokens,
		string _message
	);

	event miniGamePrizeAwarded(
		uint256 indexed _miniGameID,
		uint256 _winningNumber,
		uint256 _prize,
		string _message
	);

	event miniGameAirdropAwarded(
		uint256 indexed _miniGameID,
		uint256 _winningNumber,
		uint256 _prize,
		string _message
	);

	event roundPrizeAwarded(
		uint256 indexed _roundID,
		uint256 _winningNumber,
		uint256 _prize,
		string _message
	);

	event cyclePrizeAwarded(
		uint256 _winningNumber,
		uint256 _prize,
		string _message
	);


	///////////////////
	//ADMIN FUNCTIONS//
	///////////////////

	function adminWithdraw() external {
		require (isAdmin[msg.sender] == true || msg.sender == adminBank);
		require (adminBalance > 0, "there must be a balance");
		uint256 balance = adminBalance;
		adminBalance = 0;
		adminBank.call.value(balance).gas(100000)();

		emit adminWithdrew(balance, msg.sender, "an admin just withdrew to the admin bank");
	}

	function foundationWithdraw() external {
		require (isAdmin[msg.sender] == true || msg.sender == foundationFund);
		require (adminBalance > 0, "there must be a balance");
		uint256 balance = foundationBalance;
		foundationBalance = 0;
		foundationFund.call.value(balance).gas(100000)();

		emit adminWithdrew(balance, msg.sender, "an admin just withdrew to the foundation fund");
	}

	//this function begins resolving the round in the event that the game has stalled
	//it can be called no sooner than 1 week after the start of a minigame 
	//can only be called once. can be restarted with restartMiniGame if 256 blocks pass
	function earlyResolveA() external onlyAdmins() onlyHumans() gameOpen() {
		require (now > miniGameStartTime[miniGameCount] + 604800 && miniGameProcessing == false, "earlyResolveA cannot be called yet"); //1 week
		require (miniGamePrizePot[miniGameCount].sub(seedAreward).sub(seedBreward) >= 0);
		
		gameActive = false;
		earlyResolveACalled = true;
		generateSeedA();
	}

	//this function comlpetes the resolution and ends the game 
	function earlyResolveB() external onlyAdmins() onlyHumans() {
		require (earlyResolveACalled == true && earlyResolveBCalled == false && miniGameProcessing == true && block.number > miniGameProcessingBegun + RNGblockDelay, "earlyResolveB cannot be called yet"); 
		
		earlyResolveBCalled = true;
		resolveCycle();

		emit resolvedEarly(msg.sender, cycleProgressivePot, "the cycle was resolved early"); 
	}

	//resets the first seed in case the processing is not completed within 256 blocks 
	function restartMiniGame() external onlyAdmins() onlyHumans() {
		require (miniGameProcessing == true && block.number > miniGameProcessingBegun + 256, "restartMiniGame cannot be called yet");
		
		generateSeedA();

		emit processingRestarted(msg.sender, "mini-game processing was restarted");
	}

	//admins can close the contract no sooner than 30 days after a full cycle completes 
	//users need to withdraw funds before this date or risk losing them
	function zeroOut() external onlyAdmins() onlyHumans() {
	    require (now >= cycleEnded + 30 days && cycleOver == true, "too early to close the contract"); 
	    
	  	//event emited before selfdestruct
	    emit contractDestroyed(msg.sender, address(this).balance, "contract destroyed"); 

	    selfdestruct(foundationFund);
	}


	//////////////////
	//USER FUNCTIONS//
	//////////////////

	function () external payable onlyHumans() gameOpen() {
		//funds sent directly to contract will trigger buy
		//no refferal on fallback 
		buyInternal(msg.value, 0x0);
	}

	function buy(address _referral) public payable onlyHumans() gameOpen() {
		buyInternal(msg.value, _referral);
	}
	
	function reinvest(uint256 _amount, address _referral) external onlyHumans() gameOpen() {
		//update userBalance at beginning of function in case user has new funds to reinvest
		updateUserBalance(msg.sender);

		require (_amount <= userBalance[msg.sender], "insufficient balance");
		require (_amount >= tokenPrice, "you must buy at least one token");

		//take funds from user persistent storage and buy
		userBalance[msg.sender] = userBalance[msg.sender].sub(_amount);
		
		buyInternal(_amount, _referral);

		emit userReinvested(msg.sender, _amount, "a user reinvested");
	}

	function withdraw() external onlyHumans() {
		//update userBalance at beginning of function in case user has new funds to reinvest
		updateUserBalance(msg.sender);

		require (userBalance[msg.sender] > 0, "no balance to withdraw");
		require (userBalance[msg.sender] <= address(this).balance, "you cannot withdraw more than the contract holds");

		//update user accounting and transfer
		uint256 toTransfer = userBalance[msg.sender];
		userBalance[msg.sender] = 0;
		msg.sender.transfer(toTransfer);

		emit userWithdrew(msg.sender, toTransfer, "a user withdrew");
	}


	//////////////////
	//VIEW FUNCTIONS//
	//////////////////

	//helper function for front end token value
	function getValueOfRemainingTokens() public view returns(uint256 _tokenValue){
		return miniGameTokensLeft[miniGameCount].mul(tokenPrice);
	}

	//helper function for front end minigame prize pot
	function getCurrentMinigamePrizePot() public view returns(uint256 _mgPrize){
	    return miniGamePrizePot[miniGameCount];
	}

	//helper function for front end round prize pot
	function getCurrentRoundPrizePot() public view returns(uint256 _rndPrize){
	    return roundPrizePot[roundCount];
	}

	//helper function to return contract balance 
	function contractBalance() external view returns(uint256 _contractBalance) {
	    return address(this).balance;
	}

	//check for user divs available
	function checkUserDivsAvailable(address _user) external view returns(uint256 _userDivsAvailable) {
		return userBalance[_user] + checkDivsMgView(_user) + checkDivsRndView(_user) + checkPrizesView(_user);
	}

	//user chance of winning minigame prize or airdrop
	function userOddsMiniGame(address _user) external view returns(uint256) {
		//returns percentage precise to two decimal places (eg 1428 == 14.28% odds)
		return userMiniGameTokens[_user][miniGameCount].mul(10 ** 5).div(miniGameTokensActive[miniGameCount]).add(5).div(10);
	}

	//user chance of winning round prize or airdrop
	function userOddsRound(address _user) external view returns(uint256) {
		//returns percentage precise to two decimal places (eg 1428 == 14.28% odds)
		return userRoundTokens[_user][roundCount].mul(10 ** 5).div(roundTokensActive[roundCount]).add(5).div(10);
	}

	//user chance of winning cycle prize
	function userOddsCycle(address _user) external view returns(uint256) {
		//returns percentage precise to two decimal places (eg 1428 == 14.28% odds)
		return userTokens[_user].mul(10 ** 5).div(cycleActiveTokens).add(5).div(10);
	}

	//helper function for minigame data
	function miniGameInfo() external view returns(
		uint256 _id,
		uint256 _miniGameTokens,
		uint256 _miniGameTokensLeft,
		uint256 _miniGamePrizePot,
		uint256 _miniGameAirdropPot,
		uint256 _miniGameStartTime
		) {

		return (
			miniGameCount,
			miniGameTokens[miniGameCount],
			miniGameTokensLeft[miniGameCount],
			miniGamePrizePot[miniGameCount],
			miniGameAirdropPot[miniGameCount],
			miniGameStartTime[miniGameCount]
		);
	}

	//helper function for round data
	function roundInfo() external view returns(
		uint256 _id,
		uint256 _roundPrize,
		uint256 _roundStart
		) {

		return (
			roundCount,
			cycleProgressivePot / 2,
			roundStartTime[roundCount]
		);
	}

	//helper function for contract data
	function contractInfo() external view returns(
		uint256 _balance,
		uint256 _volume,
		uint256 _totalBuys,
		uint256 _totalUsers,
		uint256 _tokenSupply,
		uint256 _tokenPrice
		) {

		return (
			address(this).balance,
			totalVolume,
			totalBuys,
			uniqueAddress.length,
			tokenSupply,
			tokenPrice
		);
	}

	//cycle data	
	function cycleInfo() external view returns(
		bool _cycleComplete,
		uint256 _currentRound,
		uint256 _currentMinigame,
		uint256 _tokenSupply,
		uint256 _progressivePot,
		bool _prizeClaimed,
		uint256 _winningNumber
		) {
		bool isActive;
		if (miniGameCount < 1000) {
			isActive = true;
			} else {
				isActive = false;
			}
		
		return (
			isActive,
			roundCount,
			miniGameCount,
			tokenSupply,
			cycleProgressivePot,
			cylcePrizeClaimed,
			cyclePrizeWinningNumber
		);
	}


	//////////////////////
	//INTERNAL FUNCTIONS//
	//////////////////////

	function startCycle() internal {
		require (gameActive == false && cycleCount == 0, "the cycle has already been started");
		
		gameActive = true;
		cycleStart();
		roundStart();
		miniGameStart();

		emit cycleStarted(msg.sender, "a new cycle just started"); 
	}

	function buyInternal(uint256 _amount, address _referral) internal {
		require (_amount >= tokenPrice, "you must buy at least one token");
		require (userMiniGameTokensMin[msg.sender][miniGameCount].length < 10, "you are buying too often in this round"); //sets up bounded loop 

		//start cycle on first buy
		if (gameActive == false && now >= activationTime) {
			startCycle();
		}

		//update divs here to prevent overwriting userLastRoundInteractedWith
		if (userLastRoundInteractedWith[msg.sender] < roundCount || userLastMiniGameInteractedWith[msg.sender] < miniGameCount) {
			updateUserBalance(msg.sender);
		}

		//if this is the first tx after processing period is over, call generateSeedB
		if (miniGameProcessing == true && block.number > miniGameProcessingBegun + RNGblockDelay) {
			generateSeedB();
		}

		//track user
		if (knownUsers[msg.sender] == false) {
			uniqueAddress.push(msg.sender);
			knownUsers[msg.sender] = true;
		}

		//assign tokens
		uint256 tokensPurchased;
		uint256 ethSpent = _amount;
		uint256 valueOfRemainingTokens = miniGameTokensLeft[miniGameCount].mul(tokenPrice);

		//if round tokens are all sold, push difference to user balance and call generateSeedA
		if (ethSpent >= valueOfRemainingTokens) {
			uint256 incomingValue = ethSpent;
			ethSpent = valueOfRemainingTokens;
			tokensPurchased = miniGameTokensLeft[miniGameCount];
			miniGameTokensLeft[miniGameCount] = 0;
			uint256 ethCredit = incomingValue.sub(ethSpent);
			userBalance[msg.sender] += ethCredit;
			generateSeedA();
		} else {
			tokensPurchased = ethSpent.div(tokenPrice);
		}

		//update user token accounting
		userTokens[msg.sender] += tokensPurchased;
		userMiniGameTokens[msg.sender][miniGameCount] += tokensPurchased;
		userRoundTokens[msg.sender][roundCount] += tokensPurchased;
		//add min ranges and save in user accounting
		userMiniGameTokensMin[msg.sender][miniGameCount].push(cycleActiveTokens + 1);
		userMiniGameTokensMax[msg.sender][miniGameCount].push(cycleActiveTokens + tokensPurchased);
		//log last eligible rounds for withdraw checking 
		userLastMiniGameInteractedWith[msg.sender] = miniGameCount;
		userLastRoundInteractedWith[msg.sender] = roundCount;	

		uint256 referralShare = (ethSpent.mul(referralRate)).div(100);
		//check referral
			if (_referral != 0x0000000000000000000000000000000000000000 && _referral != msg.sender) {
	      // assign refferal
	      userBalance[_referral] += referralShare;
	   	} else if (_referral == 0x0000000000000000000000000000000000000000 || _referral == msg.sender){
	   		//if no referral used, add to progessive pot 
	   		cycleProgressivePot += referralShare;
	   	}

		//divide amount by various percentages and distribute
		uint256 adminShare = (ethSpent.mul(adminFeeRate)).div(100);
		adminBalance += adminShare;

		uint256 mgDivs = (ethSpent.mul(miniGameDivRate)).div(100);
		miniGameDivs[miniGameCount] += mgDivs;

		uint256 roundDivShare = ethSpent.mul(roundDivRate).div(100);
		roundDivs[roundCount] += roundDivShare;

		uint256 miniGamePrize = ethSpent.mul(miniGamePotRate).div(100);
		miniGamePrizePot[miniGameCount] += miniGamePrize;

		uint256 miniGameAirdrop = ethSpent.mul(miniGameAirdropRate).div(100);
		miniGameAirdropPot[miniGameCount] += miniGameAirdrop;

		uint256 cyclePot = ethSpent.mul(progressivePotRate).div(100);
		cycleProgressivePot += cyclePot;

     	//update global token accounting 
     	if (miniGameTokensLeft[miniGameCount] > 0) {
			miniGameTokensLeft[miniGameCount] = miniGameTokensLeft[miniGameCount].sub(tokensPurchased);
		}
		cycleActiveTokens += tokensPurchased;
		roundTokensActive[roundCount] += tokensPurchased;
		miniGameTokensActive[miniGameCount] += tokensPurchased;
		totalVolume += ethSpent;
		totalBuys++;

        //update user balance, if necessary. done here to keep ensure updateUserBalance never has to search through multiple minigames 
		updateUserBalance(msg.sender);

		emit userBought(msg.sender, tokensPurchased, miniGameCount, "a user just bought tokens");
	}

	function checkDivs(address _user) internal {
		//set up local shorthand
		uint256 _mg = userLastMiniGameInteractedWith[_user];
		uint256 _rnd = userLastRoundInteractedWith[_user];

		//calculate minigame divs 
		userShareMiniGame[_user][_mg] = userMiniGameTokens[_user][_mg].mul(10 ** (precisionFactor + 1)).div(miniGameTokens[_mg] + 5).div(10);
	    userDivsMiniGameTotal[_user][_mg] = miniGameDivs[_mg].mul(userShareMiniGame[_user][_mg]).div(10 ** precisionFactor);
	    userDivsMiniGameUnclaimed[_user][_mg] = userDivsMiniGameTotal[_user][_mg].sub(userDivsMiniGameClaimed[_user][_mg]);
	    //add to user balance
	    if (userDivsMiniGameUnclaimed[_user][_mg] > 0) {
			//sanity check
			assert(userDivsMiniGameUnclaimed[_user][_mg] <= miniGameDivs[_mg]);
			assert(userDivsMiniGameUnclaimed[_user][_mg] <= address(this).balance);
			//update user accounting
			userDivsMiniGameClaimed[_user][_mg] = userDivsMiniGameTotal[_user][_mg];
			uint256 shareTempMg = userDivsMiniGameUnclaimed[_user][_mg];
			userDivsMiniGameUnclaimed[_user][_mg] = 0;
			userBalance[_user] += shareTempMg;
			miniGameDivsClaimed[_mg] += shareTempMg;
		    }
	    //calculate round divs 
		userShareRound[_user][_rnd] = userRoundTokens[_user][_rnd].mul(10 ** (precisionFactor + 1)).div(roundTokensActive[_rnd] + 5).div(10);
	    userDivsRoundTotal[_user][_rnd] = roundDivs[_rnd].mul(userShareRound[_user][_rnd]).div(10 ** precisionFactor);
	    userDivsRoundUnclaimed[_user][_rnd] = userDivsRoundTotal[_user][_rnd].sub(userDivsRoundClaimed[_user][_rnd]);
	    //add to user balance
	    if (userDivsRoundUnclaimed[_user][_rnd] > 0) {
			//sanity check
			assert(userDivsRoundUnclaimed[_user][_rnd] <= roundDivs[_rnd]);
			assert(userDivsRoundUnclaimed[_user][_rnd] <= address(this).balance);
			//update user accounting
			userDivsRoundClaimed[_user][_rnd] = userDivsRoundTotal[_user][_rnd];
			uint256 shareTempRnd = userDivsRoundUnclaimed[_user][_rnd];
			userDivsRoundUnclaimed[_user][_rnd] = 0;
			userBalance[_user] += shareTempRnd;
			roundDivsClaimed[_rnd] += shareTempRnd;
	    }	
	}

	function checkPrizes(address _user) internal {
		//push cycle prizes to persistent storage
		if (cycleOver == true && userCycleChecked[_user] == false) {
			//get minigame cycle prize was in 
			uint256 mg = cyclePrizeInMinigame;
			//check if user won cycle prize 
			if (cylcePrizeClaimed == false && userMiniGameTokensMax[_user][mg].length > 0) {
				//check if user won minigame 
				//loop iterations bounded to a max of 10 on buy()
	  			for (uint256 i = 0; i < userMiniGameTokensMin[_user][mg].length; i++) {
	  				if (cyclePrizeWinningNumber >= userMiniGameTokensMin[_user][mg][i] && cyclePrizeWinningNumber <= userMiniGameTokensMax[_user][mg][i]) {
	  					userBalance[_user] += cycleProgressivePot;
	  					cylcePrizeClaimed = true;
						cyclePrizeWinner = msg.sender;				
	  					break;
	  				}
	  			}
			}
			userCycleChecked[_user] = true;
		}
		//push round prizes to persistent storage
		if (roundPrizeClaimed[userLastRoundInteractedWith[_user]] == false && roundPrizeTokenRangeIdentified[userLastRoundInteractedWith[_user]]) {
			//get minigame round prize was in 
			uint256 rnd = userLastRoundInteractedWith[_user];
			uint256 mgp = roundPrizeInMinigame[rnd];
			//check if user won round prize
			for (i = 0; i < userMiniGameTokensMin[_user][mgp].length; i++) {
				if (roundPrizeNumber[rnd] >= userMiniGameTokensMin[_user][mgp][i] && roundPrizeNumber[rnd] <= userMiniGameTokensMax[_user][mgp][i]) {
					userBalance[_user] += roundPrizePot[mgp];
					roundPrizeClaimed[rnd] = true;
					roundPrizeWinner[rnd] = msg.sender;		
					break;
				}
			}
			userLastRoundChecked[_user] = userLastRoundInteractedWith[_user];
		}
		//push minigame prizes to persistent storage
		if (userLastMiniGameChecked[_user] < userLastMiniGameInteractedWith[_user] && miniGameCount > userLastMiniGameInteractedWith[_user]) {
			//check if user won minigame 
			mg = userLastMiniGameInteractedWith[_user];
			for (i = 0; i < userMiniGameTokensMin[_user][mg].length; i++) {
				if (miniGamePrizeNumber[mg] >= userMiniGameTokensMin[_user][mg][i] && miniGamePrizeNumber[mg] <= userMiniGameTokensMax[_user][mg][i]) {
					userBalance[_user] += miniGamePrizePot[mg];
					miniGamePrizeClaimed[mg] = true;
					miniGamePrizeWinner[mg] = msg.sender;			
					break;
				}
			}
			//check if user won airdrop
			for (i = 0; i < userMiniGameTokensMin[_user][mg].length; i++) {
				if (miniGameAirdropNumber[mg] >= userMiniGameTokensMin[_user][mg][i] && miniGameAirdropNumber[mg] <= userMiniGameTokensMax[_user][mg][i]) {
					userBalance[_user] += miniGameAirdropPot[mg];
					miniGameAirdropClaimed[mg] = true;
					miniGameAirdropWinner[mg] = msg.sender;
					break;
				}
			}
			//update last mini game checked 
			userLastMiniGameChecked[_user] = userLastMiniGameInteractedWith[_user];
		}
	}

	function updateUserBalance(address _user) internal {
		checkDivs(_user);
		checkPrizes(_user);
	}

	function miniGameStart() internal {
		require (cycleOver == false, "the cycle cannot be over");
		
		miniGameCount++;
		miniGameStartTime[miniGameCount] = now;
		//set up special case for correct token range on first minigame 
		if (tokenSupply != 0) {
			miniGameTokenRangeMin[miniGameCount] = tokenSupply + 1;
		} else {
			miniGameTokenRangeMin[miniGameCount] = 0;
		}
		//genreate tokens and update accounting 
		miniGameTokens[miniGameCount] = generateTokens();
		miniGameTokensLeft[miniGameCount] = miniGameTokens[miniGameCount];
		miniGameTokenRangeMax[miniGameCount] = tokenSupply;
		//increment token price after 1st minigame 
		if (miniGameCount > 1) {
			tokenPrice += tokenPriceIncrement;
		}
		//award prizes and start new round if current round is complete 
		if (miniGameCount % miniGamesPerRound == 0 && miniGameCount > 1) {
			awardRoundPrize();
			roundStart();
			tokenPrice = 0.001 ether + 0.0005 ether * roundCount.sub(1);
		}
		//award prize if cycle is complete 
		if (miniGameCount % (miniGamesPerCycle + 1) == 0 && miniGameCount > 1) {
			awardCyclePrize();
		}

		emit newMinigameStarted(miniGameCount, miniGameTokens[miniGameCount], "new minigame started");
	}

	function roundStart() internal {
		require (cycleOver == false, "the cycle cannot be over");

		roundCount++;
		roundStartTime[roundCount] = now;
		//set up special case for correct token range on first round 
		if (tokenSupply != 0) {
			roundTokenRangeMin[roundCount] = miniGameTokenRangeMax[miniGameCount.sub(1)] + 1;
		} else {
			roundTokenRangeMin[roundCount] = 0;
		}
		//log max only when round is complete 
		if (roundCount >= 2) {
			roundTokenRangeMax[roundCount.sub(1)] = miniGameTokenRangeMax[miniGameCount.sub(1)];
			roundTokens[roundCount.sub(1)] = tokenSupply.sub(roundTokenRangeMin[roundCount.sub(1)]);
		}
	}

	function cycleStart() internal {
		require (cycleOver == false, "the cycle cannot be over");

		cycleCount++;
		cycleStartTime = now;
	}

	function generateTokens() internal returns(uint256 _tokens) {
		bytes32 hash = keccak256(abi.encodePacked(salt, hashA, hashB));
		uint256 randTokens = uint256(hash).mod(minTokensPerMiniGame);
    	uint256 newMinGameTokens = randTokens + minTokensPerMiniGame;
		tokenSupply += newMinGameTokens;
		salt++;

		return newMinGameTokens;
	}

	function generateSeedA() internal {
		require (miniGameProcessing == false || miniGameProcessing == true && block.number > miniGameProcessingBegun + 256, "seed A cannot be regenerated right now");
		require (miniGameTokensLeft[miniGameCount] == 0 || earlyResolveACalled == true, "active tokens remain in this minigame");
		
		miniGameProcessing = true;
		miniGameProcessingBegun = block.number;
		//generate seed 
		hashA = blockhash(miniGameProcessingBegun - 1);
		//log end times
		if (miniGameCount > 1) {
			miniGameEndTime[miniGameCount] = now;
		}
		if (miniGameCount % miniGamesPerRound == 0) {
			roundEndTime[roundCount] = now;
		}
		//award processing bounty
		if (miniGameSeedAawarded[miniGameCount] == false) {
			userBalance[msg.sender] += seedAreward;
			miniGameSeedAawarded[miniGameCount] = true;
		}
		salt++;

		emit processingStarted(msg.sender, miniGameCount, block.number, "processing started");
	}

	function generateSeedB() internal {
		//gererate seed 
		hashB = blockhash(miniGameProcessingBegun + RNGblockDelay);
		//awared prizes 
		awardMiniGamePrize();
		awardMiniGameAirdrop();
		//award processing bounty 
		if (miniGameSeedBawarded[miniGameCount] == false) {
			userBalance[msg.sender] += seedBreward;
			miniGameSeedBawarded[miniGameCount] = true;
		}
		//start next minigame
		miniGameStart();
		miniGameProcessing = false;
		salt++;

		emit processingFinished(msg.sender, miniGameCount, block.number, "processing finished");
	}

	function awardMiniGamePrize() internal {
		bytes32 hash = keccak256(abi.encodePacked(salt, hashA, hashB));
	    uint256 winningNumber = uint256(hash).mod(miniGameTokens[miniGameCount].sub(miniGameTokensLeft[miniGameCount]));
	    miniGamePrizeNumber[miniGameCount] = winningNumber + miniGameTokenRangeMin[miniGameCount];
	    miniGamePrizePot[miniGameCount] = miniGamePrizePot[miniGameCount].sub(seedAreward).sub(seedBreward);
	    salt++;

	    emit miniGamePrizeAwarded(miniGameCount, winningNumber, miniGamePrizePot[miniGameCount], "minigame prize awarded");
	}

	function awardMiniGameAirdrop() internal {
		bytes32 hash = keccak256(abi.encodePacked(salt, hashA, hashB));
	    uint256 winningNumber = uint256(hash).mod(miniGameTokens[miniGameCount].sub(miniGameTokensLeft[miniGameCount]));
	    miniGameAirdropNumber[miniGameCount] = winningNumber + miniGameTokenRangeMin[miniGameCount];
	    salt++;

	    emit miniGameAirdropAwarded(miniGameCount, winningNumber, miniGameAirdropPot[miniGameCount], "minigame airdrop awarded");
	}

	function awardRoundPrize() internal {
		bytes32 hash = keccak256(abi.encodePacked(salt, hashA, hashB));
		uint256 currentRoundTokens;
		if (miniGameCount > 1) {
			currentRoundTokens = miniGameTokenRangeMax[miniGameCount.sub(1)].sub(roundTokenRangeMin[roundCount]);
		//handles edge case of early resolve during the first minigame 
		} else if (miniGameCount == 1) {
			currentRoundTokens = miniGameTokensActive[1];
		}
	    uint256 winningNumber = uint256(hash).mod(currentRoundTokens);
	    roundPrizeNumber[roundCount] = winningNumber + roundTokenRangeMin[roundCount];
	    //calculate round prize here 
	    uint256 roundPrize = cycleProgressivePot.mul(roundPotRate).div(100);
		uint256 adminShare = cycleProgressivePot.mul(4).div(100);
		foundationBalance += adminShare;
	    roundPrizePot[roundCount] = roundPrize;
	    cycleProgressivePot = roundPrize;
	    narrowRoundPrize(roundCount);
	    salt++;

		emit roundPrizeAwarded(roundCount, winningNumber, roundPrize, "round prize awarded");
	}

	function awardCyclePrize() internal {
		bytes32 hash = keccak256(abi.encodePacked(salt, hashA, hashB));
	    uint256 winningNumber;
	    if (miniGameCount > 1) {
	    	winningNumber = uint256(hash).mod(miniGameTokenRangeMax[miniGameCount - 1]);
	    //handles edge case of early resolve during the first minigame 
	    } else if (miniGameCount == 1) {
	    	winningNumber = uint256(hash).mod(miniGameTokensActive[1]);
	    }
	    cyclePrizeWinningNumber = winningNumber;
	    gameActive = false;
	    cycleEnded = now;
	    cycleOver = true;
	    narrowCyclePrize();
	    salt++;

		emit cyclePrizeAwarded(winningNumber, cycleProgressivePot, "cycle prize awarded");
	}

	function resolveCycle() internal {
		//generate hashB here in instead of calling generateSeedB
		hashB = blockhash(miniGameProcessingBegun + RNGblockDelay);
		//award prizes 
		awardMiniGamePrize();
		awardMiniGameAirdrop();
		awardRoundPrize();
		awardCyclePrize();
		//close game
		miniGameProcessing = false;
		gameActive = false;
	}

	//narrows down the token range of a round to a specific miniGame
	//reduces the search space on user prize updates 
	function narrowRoundPrize(uint256 _ID) internal returns(uint256 _miniGameID) {
		//set up local accounting
		uint256 miniGameRangeMin; 
		uint256 miniGameRangeMax;
		if (_ID == 1) {
			miniGameRangeMin = 1;
			miniGameRangeMax = miniGamesPerRound;
		} else if (_ID >= 2) {
			miniGameRangeMin = _ID.mul(miniGamesPerRound);
			miniGameRangeMax = miniGameRangeMin + miniGamesPerRound - 1;
		}	
		//loop through each minigame to check prize number
		//log globaly so this only needs to be called once per prize 
	    for (uint256 i = miniGameRangeMin; i <= miniGameRangeMax; i++) {
		    if (roundPrizeNumber[_ID] >= miniGameTokenRangeMin[i] && roundPrizeNumber[_ID] <= miniGameTokenRangeMax[i]) {
	        roundPrizeInMinigame[_ID] = i;
	        roundPrizeTokenRangeIdentified[_ID] = true;
	        return roundPrizeInMinigame[_ID];
	        break;
		    }
	    }	
	}

	//narrows down the token range of a round to a specific miniGame
	//reduces the search space on user prize updates 
	function narrowCyclePrize() internal returns(uint256 _miniGameID) {
		//first identify round 
	    for (uint256 i = 1; i <= roundCount; i++) {
	      if (cyclePrizeWinningNumber >= roundTokenRangeMin[i] && cyclePrizeWinningNumber <= roundTokenRangeMax[i]) {
	        cyclePrizeInRound = i;
	        break;
	      }
	    }
	    //set up minigame local accounting 
	    uint256 miniGameRangeMin; 
		uint256 miniGameRangeMax;
		uint256 _ID = cyclePrizeInRound;
		if (_ID == 1) {
			miniGameRangeMin = 1;
			miniGameRangeMax = miniGamesPerRound;
		} else if (_ID >= 2) {
			miniGameRangeMin = _ID.mul(miniGamesPerRound);
			miniGameRangeMax = miniGameRangeMin + miniGamesPerRound - 1;
		}	
		//loop through each minigame to check prize number
		//log globaly so this only needs to be called once per prize  
	    for (i = miniGameRangeMin; i <= miniGameRangeMax; i++) {
			if (cyclePrizeWinningNumber >= miniGameTokenRangeMin[i] && cyclePrizeWinningNumber <= miniGameTokenRangeMax[i]) {
				cyclePrizeInMinigame = i;
				cyclePrizeTokenRangeIdentified = true;
				return cyclePrizeInMinigame;
				break;
			}
	    }	
	}

	//helper function for up to date front end balances without state change
	function checkDivsMgView(address _user) internal view returns(uint256 _divs) {
		//set up local shorthand
		uint256 _mg = userLastMiniGameChecked[_user];
		uint256 mgShare = userShareMiniGame[_user][_mg];
		uint256 mgTotal = userDivsMiniGameTotal[_user][_mg];
		uint256 mgUnclaimed = userDivsMiniGameUnclaimed[_user][_mg];
		//calculate minigame divs 
		mgShare = userMiniGameTokens[_user][_mg].mul(10 ** (precisionFactor + 1)).div(miniGameTokens[_mg] + 5).div(10);
	    mgTotal = miniGameDivs[_mg].mul(mgShare).div(10 ** precisionFactor);
	    mgUnclaimed = mgTotal.sub(userDivsMiniGameClaimed[_user][_mg]);

	    return mgUnclaimed;
	}
	
	//helper function for up to date front end balances without state change
	function checkDivsRndView(address _user) internal view returns(uint256 _divs) {
		//set up local shorthand
		uint256 _rnd = userLastRoundChecked[_user];
		uint256 rndShare = userShareRound[_user][_rnd];
		uint256 rndTotal = userDivsRoundTotal[_user][_rnd];
		uint256 rndUnclaimed = userDivsRoundUnclaimed[_user][_rnd];
        //calculate round divs 
		rndShare = userRoundTokens[_user][_rnd].mul(10 ** (precisionFactor + 1)).div(roundTokensActive[_rnd] + 5).div(10);
	    rndTotal = roundDivs[_rnd].mul(rndShare).div(10 ** precisionFactor);
	    rndUnclaimed = rndTotal.sub(userDivsRoundClaimed[_user][_rnd]);

	    return rndUnclaimed;
	}

	//helper function for up to date front end balances without state change
	function checkPrizesView(address _user) internal view returns(uint256 _prizes) {
		//local accounting
		uint256 prizeValue;
		//push cycle prizes to persistent storage
		if (cycleOver == true && userCycleChecked[_user] == false) {
			//get minigame cycle prize was in 
			uint256 mg;
			if (cyclePrizeTokenRangeIdentified == true) {
				mg = cyclePrizeInMinigame;
			} else {
				narrowCyclePrizeView();
				mg = cyclePrizeInMinigame;
			}
			//check if user won cycle prize 
			if (cylcePrizeClaimed == false && userMiniGameTokensMax[_user][mg].length > 0) {
				//check if user won minigame 
				//loop iterations bounded to a max of 10 on buy()
				for (uint256 i = 0; i < userMiniGameTokensMin[_user][mg].length; i++) {
					if (cyclePrizeWinningNumber >= userMiniGameTokensMin[_user][mg][i] && cyclePrizeWinningNumber <= userMiniGameTokensMax[_user][mg][i]) {
						prizeValue += cycleProgressivePot;			
						break;
					}
				}
			}
		}
		//push round prizes to persistent storage
		if (userLastRoundChecked[_user] < userLastRoundInteractedWith[_user] && roundCount > userLastRoundInteractedWith[_user]) {
			//get minigame round prize was in 
			uint256 mgp;
			uint256 _ID = userLastRoundChecked[_user];
			if (roundPrizeTokenRangeIdentified[_ID] == true) {
				mgp = roundPrizeInMinigame[_ID];
			} else {
				narrowRoundPrizeView(_ID);
				mgp = roundPrizeInMinigame[_ID];
			}
			//check if user won round prize
			for (i = 0; i < userMiniGameTokensMin[_user][mgp].length; i++) {
				if (roundPrizeNumber[_ID] >= userMiniGameTokensMin[_user][mgp][i] && roundPrizeNumber[_ID] <= userMiniGameTokensMax[_user][mgp][i]) {
					prizeValue += roundPrizePot[mgp];	
					break;
				}
			}
		}
		//push minigame prizes to persistent storage
		if (userLastMiniGameChecked[_user] < userLastMiniGameInteractedWith[_user] && miniGameCount > userLastMiniGameInteractedWith[_user]) {
			//check if user won minigame 
			mg = userLastMiniGameInteractedWith[_user];
			for (i = 0; i < userMiniGameTokensMin[_user][mg].length; i++) {
				if (miniGamePrizeNumber[mg] >= userMiniGameTokensMin[_user][mg][i] && miniGamePrizeNumber[mg] <= userMiniGameTokensMax[_user][mg][i]) {
					prizeValue += miniGamePrizePot[mg];			
					break;
				}
			}
			//check if user won airdrop
			for (i = 0; i < userMiniGameTokensMin[_user][mg].length; i++) {
				if (miniGameAirdropNumber[mg] >= userMiniGameTokensMin[_user][mg][i] && miniGameAirdropNumber[mg] <= userMiniGameTokensMax[_user][mg][i]) {
					prizeValue += miniGameAirdropPot[mg];
					break;
				}
			}
		}
		return prizeValue;
	}

	//helper function for up to date front end balances without state change
	function narrowRoundPrizeView(uint256 _ID) internal view returns(uint256 _miniGameID) {
		//set up local accounting
		uint256 winningNumber = roundPrizeNumber[_ID];
		uint256 miniGameRangeMin; 
		uint256 miniGameRangeMax;
		if (_ID == 1) {
			miniGameRangeMin = 1;
			miniGameRangeMax = miniGamesPerRound;
		} else if (_ID >= 2) {
			miniGameRangeMin = _ID.mul(miniGamesPerRound);
			miniGameRangeMax = miniGameRangeMin + miniGamesPerRound - 1;
		}	
		//loop through each minigame to check prize number
		//log globaly so this only needs to be called once per prize 
	    for (uint256 i = miniGameRangeMin; i <= miniGameRangeMax; i++) {
			if (winningNumber >= miniGameTokenRangeMin[i] && winningNumber <= miniGameTokenRangeMax[i]) {
				return i;
				break;
			}
	    }		
	}

	//helper function for up to date front end balances without state change
	function narrowCyclePrizeView() internal view returns(uint256 _miniGameID) {
		//set up local accounting
		uint256 winningNumber = cyclePrizeWinningNumber;
		uint256 rnd;
		//first identify round 
	    for (uint256 i = 1; i <= roundCount; i++) {
			if (winningNumber >= roundTokenRangeMin[i] && winningNumber <= roundTokenRangeMax[i]) {
				rnd = i;
				break;
			}
	    }
	    //set up minigame local accounting 
	    uint256 miniGameRangeMin; 
			uint256 miniGameRangeMax;
			uint256 _ID = rnd;
			if (_ID == 1) {
				miniGameRangeMin = 1;
				miniGameRangeMax = miniGamesPerRound;
			} else if (_ID >= 2) {
				miniGameRangeMin = _ID.mul(miniGamesPerRound);
				miniGameRangeMax = miniGameRangeMin + miniGamesPerRound - 1;
			}	
			//loop through each minigame to check prize number
			//log globaly so this only needs to be called once per prize  
	    for (i = miniGameRangeMin; i <= miniGameRangeMax; i++) {
			if (winningNumber >= miniGameTokenRangeMin[i] && winningNumber <= miniGameTokenRangeMax[i]) {
				return i;
				break;
			}
	    }			
	}
}