// SPDX-License-Identifier: MIT
pragma solidity ^ 0.6.6;

contract MagicLamp{
	Pyramid PiZZa = Pyramid(0x91683899ed812C1AC49590779cb72DA6BF7971fE); // Color Token Contract We all know and love
	address THIS = address(this);
	uint wishes; // life of the Genie... no wishes,,, actually granted
	address carpetRider; // stays afloat with a colored kingdom
	uint carpetRiderHP; // health of the carpet rider
	address GENIE; // did a lot of work, and sits back and earns money... burn rugs
	uint GENIE_g; // The generation the genie is on
	ERC20 colorToken;
	address address0 = address(0);
	mapping(address => address) gateway; // this is affiliate uplink thing.
	mapping(address => uint) pocket; // what you earn through affiliates
	mapping(address => bool) initiated;
	mapping(address => bool) ofRugs; // address that belong to rug tokens

	// need 7s for 6s
	address[7][7][7] rugTokens; //for identifying rugToken when buying colored pizza

	address the66thCarpetKing; // Dev.

	//LAZY U.I. ... Because we have no events and... yeah
	mapping(uint => address ) rugTokenList;
	uint rugTokenListSize;
	uint genesis;

	constructor() public{
		the66thCarpetKing = msg.sender;
		GENIE = the66thCarpetKing;
		carpetRider = the66thCarpetKing;
		colorToken = PiZZa.resolveToken();
		gateway[the66thCarpetKing] = the66thCarpetKing;
		initiated[the66thCarpetKing] = true;
		genesis = now;
	}

	function ClaimKingdom___FlyFloatingCastle(uint8 r, uint8 g, uint8 b) external{
		address sender = msg.sender;
		if((
			(r>=0 && r<=3 && g>=0 && g<=3 && b>=0 && b<=3)
			||(rugTokenListSize == 0)
		)&& rugTokens[r][g][b] == address0
		){
			RugToken newRugSource = new RugToken(r, g, b, weight(sender)*10, PiZZa);
			address rugAddress = address(newRugSource);
			rugTokens[r][g][b] = rugAddress;
			timerEnds[rugAddress] = genesis;
			rugTokenList[rugTokenListSize] = rugAddress;
			ofRugs[rugAddress] = true;
			carpetKing[rugAddress] = sender;
			
			if( r!=6 ){
				X[rugAddress] = (rugTokenListSize-1)/8+1;
				Y[rugAddress] = (rugTokenListSize-1)%8+1;
			}else{
				floatingCastle = rugAddress;
			}

			//last guy starts the shits storm
			if(rugTokenListSize == 64){ 
				caravan[rugAddress] = true;
			}

			rugTokenListSize += 1;
		}else{
			//Flying the Airship Castle
			address kingdom = rugTokenList[r];
			if( kingdom == floatingCastle && sender == carpetRider && g>=1 && g<=8 && b>=1 && b<=8 && cooldown[floatingCastle] < now){
				X[floatingCastle] = g;
				Y[floatingCastle] = b;
				freeze(floatingCastle);
			}else{
				revert();
			}
		}
	}

	
	function weight(address addr) internal view returns(uint colors){
		return PiZZa.resolveWeight(addr);//crush friction fee and get around exploit of resolves hopping wallets
	}
	function rugsOf(address addr, address rugColor) public view returns(uint rugCount){
		return RugToken(rugColor).balanceOf(addr);
	}
	function ofRug(address addr) public view returns(bool){
		return ofRugs[addr];
	}

	function buy(address _gateway, uint _red, uint _green, uint _blue) public payable returns(uint bondsCreated, address rugAddress){
		address sender = msg.sender;

		//keep colors in check
		if(_red>1e18) _red = 1e18;
		if(_green>1e18) _green = 1e18;
		if(_blue>1e18) _blue = 1e18;
		
		(uint R, uint G, uint B) = colorToCluster(_red,  _green,  _blue);

		rugAddress = rugTokens[ R ][ G ][ B ];

		if( !initiated[sender] ){
			if(weight(_gateway)==0) _gateway = carpetKing[rugAddress];
			gateway[sender] = _gateway;
			initiated[sender] = true;
		}

		uint createdPiZZa = PiZZa.buy{value: msg.value * 934 / 1000}(sender, _red, _green, _blue);

		if( msg.value >= 0.001 ether){
			uint power = createdPiZZa/1e18;
			if(carpetRider != sender){
				if(rugAddress != floatingCastle){

					if( carpetRiderHP <= power ){
						carpetRiderHP = burnCount[ GENIE_g ][rugBurnRecordSetter[GENIE_g]]+power;

						//erect new floating island
						if(caravan[rugAddress]){
							caravan[floatingCastle] = true;
							caravan[rugAddress] = false;
						}

						//Just a little "chaos theory" ...
						//this is dropping the clock pressure of people who bought at higher prices.
						//hard earned clockPressure dropping.
						dropClockPressure(floatingCastle);
						X[floatingCastle] = X[rugAddress];
						Y[floatingCastle] = Y[rugAddress];
						floatingCastle = rugAddress;

						endRound(THIS);//new Caravan Round

						carpetRider = sender;
					}else{		
						carpetRiderHP -= power;
					}
				}
			}else{
				if(rugAddress == floatingCastle){
					carpetRiderHP += power;
				}
			}

			if(wishes > 0)
				wishes -= 1;
			
			uint rugsToMake = createdPiZZa/1e18;
			if(rugsToMake>0)
				RugToken(rugAddress).mint(msg.sender, rugsToMake);

			if(wishes == 0){
				if(rugBurnRecordSetter[GENIE_g] != address0){
					GENIE = rugBurnRecordSetter[GENIE_g];
					wishes = burnCount[ GENIE_g ][rugBurnRecordSetter[GENIE_g]];
					GENIE_g += 1;
				}
			}
		}

		payDownline(rugAddress);
		return (createdPiZZa, rugAddress);
	}


	function payLevel(address lvl, uint ETH) internal returns(uint riderRugs){
		address sender = msg.sender;
		if( weight( gateway[lvl] ) > weight(sender) ){
			pocket[ gateway[lvl] ] += ETH;
		}else{
			riderRugs += ETH;
			gateway[lvl] = lvl==sender?carpetRider:sender;
		}
	}

	function payDownline(address rugAddress) internal{
		//Affiliate Payouts
		address sender = msg.sender;
		uint riderRugs;
		uint val = msg.value;

		riderRugs += payLevel(sender, val * 3 / 100);
		riderRugs += payLevel(gateway[sender], val * 2 / 100);
		riderRugs += payLevel(gateway[gateway[sender]], val / 100);

		pocket[carpetRider] += riderRugs;
		
		uint _4Kingdom = val * 3 / 1000;
		// Pay the king
		pocket[ carpetKing[rugAddress] ] += _4Kingdom/2;
		// Pays the Fomo Pot
		pocket[ rugAddress ] += _4Kingdom-_4Kingdom/2;

		// Pay Genie
		pocket[ ( (now - genesis) < 5702400 /* 66 Days of Dev fee */ )? the66thCarpetKing: GENIE ] +=  val * 3 / 1000;
	}

	function colorToCluster(uint r, uint g, uint b) public pure returns(uint8 rC, uint8 gC, uint8 bC){
		uint GRAY_BLOCK = 1e18/5;
		if(r/GRAY_BLOCK == 2 && g/GRAY_BLOCK == 2 && b/GRAY_BLOCK == 2){
			rC = 6;
			gC = 6;
			bC = 6;
		}else{
			uint C = (uint)(1e18) / 4;
			uint x = r;
			rC = x>=C*3?3:x>=C*2?2:x>=C?1:0;
			x = g;
			gC = x>=C*3?3:x>=C*2?2:x>=C?1:0;
			x = b;
			bC = x>=C*3?3:x>=C*2?2:x>=C?1:0;
		}

		return (rC, gC, bC);
	}

	function tokenFallback(address from, uint value, bytes calldata _data) external{
		address TOKEN = address( msg.sender );
		if(TOKEN == address(colorToken) ){
			if( ofRug(from) ){
				//These resolves came from new carpetKings
				address newKing = bytesToAddress(_data);
				if(newKing != address0/* keep this so you can pass in heals */){
					carpetKing[from] = newKing;
				}
			}
		}else if( ofRug(TOKEN) ){
			if( !ofRug(from) ){
				uint rugMagic = value;
				if(from == carpetRider){
					if(carpetRider != GENIE && caravan[TOKEN] ){
						if(rugMagic >= wishes){
							//kill
							if(wishes>0){
								wishes = 0;	
								colorToken.transfer( carpetRider, colorToken.balanceOf(THIS) );
							}
							//they're already dead
						}else{
							//damage
							wishes -= rugMagic;
						}
					}else{revert();}
				}else{
					revert();
				}
			}else{
				colorImports[from][TOKEN] += value;
				if( ( caravan[from] || from == GENIE ) && inRing(from,TOKEN,false) )
					moreShares(THIS, fomoRound[THIS], bytesToAddress(_data), value);
			}
		}else if( TOKEN == address(PiZZa) ){
			require( value > 0 );
			address who = bytesToAddress(_data);
			if (who == address0){
				prizePizza[from] += value;
				return;
			}
			
			require( ofRug(from) );

			uint additionalTime = now - RugToken(from).ageOfPiZZa()/scaleFactor;

			//taking this out because... living on the edge
			//require( additionalTime < (now - genesis) );			

			//it's just more fair to add pressure based off of ETH,
			//and not up the exponential diffictulty of the bonding curve
			uint pETH = pizza2ETH(value);

			//the more clock weight, the more shares in fomo.
			uint clocks = additionalTime * pETH;
		
			//makes adding to the timer more and more difficult as more eth comes in.
			//this can become gamable with big players.
			//but that's why we're able to drop the clock pressure in special events
			clockPressureContribution[from] += pETH;
			pizzaClockPressure += pETH;

			additionalTime = additionalTime
			//Give them a few different tools to fight the clock pressure from being gamed by big money.
			*( (caravan[from] || GENIE == from)?( value/1e18 /* YES... */ ):1 )
			//clock pressure
			/(pizzaClockPressure / pETH );
			
			require( additionalTime > 0 && value >= 1e18 );
			
			uint fomo_round = fomoRound[from];
			currentLead[from][fomo_round] = who;

			uint timer = timerEnds[from];
			if( fomoInitiated[from][fomo_round]  && now <= timer){
				timer += additionalTime;
			}else{
				fomoInitiated[from][fomo_round] = true;
				timer = now + additionalTime;
			}
			timerEnds[from] = timer;
			
			moreShares(from, fomo_round, who, clocks);
			uint FOMO_PIZZA;
			uint caravanClockWeight = totalClockWeight[THIS][fomoRound[THIS]];

			if( ( caravan[from] || from == GENIE ) && caravanClockWeight > 0){
				FOMO_PIZZA += value/2;
				pizzaPerClock[THIS][fomoRound[THIS]] += (value/4) * scaleFactor / caravanClockWeight;
			}else{
				FOMO_PIZZA += value/4;
				kingsPizza[from] += (value/4);
			}
			prizePizza[from] += FOMO_PIZZA;
			pizzaPerClock[from][fomo_round] += (value - FOMO_PIZZA - (value/4)) * scaleFactor / totalClockWeight[from][fomo_round];
		}else{
			revert();
		}
	}

	function moreShares(address from, uint fomo_round, address who, uint shares) internal{
		totalClockWeight[from][fomo_round] += shares;
		clockWeight[from][fomo_round][who] += shares;
		payouts[from][fomo_round][who] += pizzaPerClock[from][fomo_round]*shares;
	}

	function bytesToAddress(bytes memory bys) internal pure returns (address addr){
		assembly {
		  addr := mload(add(bys,20))
		} 
	}

	mapping(uint => address) rugBurnRecordSetter;
	mapping(uint => mapping(address => uint)) burnCount;

	function countBurnedRugs( address targetColor, address who, uint rugsBurned) external{
		//address sender = msg.sender;
		//yes... for some reason "msg.sender" is smaller.
		require( ofRug(msg.sender) );

		if(floatingCastle == targetColor || (who == THIS && caravan[msg.sender]/*if they aren't carrying the magic lamp the magic doesn't rub off*/) ){
			
			if(who == THIS){
				who = msg.sender;
				
				// grey can never be enchanted
				rugsBurned *= 2 * purity(who);
			}

			burnCount[GENIE_g][who] += rugsBurned;
			if( burnCount[GENIE_g][who] > burnCount[GENIE_g][ rugBurnRecordSetter[GENIE_g] ] )
				rugBurnRecordSetter[GENIE_g] = who;
		}
	}

	function purity(address kingdom)internal view returns(uint){
		(uint r, uint g, uint b) = RugToken(kingdom)._RGB();
		return (  r==6?0:( 1 + (r==0||r==3?1:0) + (g==0||g==3?1:0) + (b==0||b==3?1:0) ) );
	}

	function dropClockPressure(address kingdom) internal returns(bool deservesPortals){
		uint _clockPressure = clockPressureContribution[kingdom];
		pizzaClockPressure -= _clockPressure;
		deservesPortals =  _clockPressure >= pizzaClockPressure;
		clockPressureContribution[kingdom] = 0;
	}


	function addressPayable(address addr) internal pure returns(address payable){return address( uint160(addr) );}

	uint pizzaClockPressure;
	mapping(address => bool) caravan;
	mapping(address => bool) shifter;
	mapping(address => uint) fomoRound;
	mapping(address => uint) timerEnds;
	mapping(address => uint) prizePizza;
	mapping(address => uint) kingsPizza;
	mapping(address => uint) cooldown;
	mapping(address => mapping(uint => bool)) fomoInitiated;
	mapping(address => uint) clockPressureContribution;
	mapping(address => mapping(uint => uint)) pizzaPerClock;
	mapping(address => mapping(uint => address)) currentLead;
	mapping(address => mapping(uint => uint)) totalClockWeight;
	mapping(address => mapping(uint => mapping(address => uint))) clockWeight;
	mapping(address => mapping(uint => mapping(address => uint))) payouts;
    uint256 constant scaleFactor = 0x10000000000000000;

	function dividendsOf(address kingdom, uint _fomoRound, address player) public view returns(uint dividends, uint yourWeight, uint totalWeight){
		return ( ( clockWeight[kingdom][_fomoRound][player] * pizzaPerClock[kingdom][_fomoRound] - payouts[kingdom][_fomoRound][player] ) / scaleFactor  ,clockWeight[kingdom][_fomoRound][player] , totalClockWeight[kingdom][_fomoRound] );  
	}

	function withdrawDividends(address[] memory kingdoms, uint[] memory fomoRounds, address destination) public{
		address sender = msg.sender;
		uint $PIZZA;
		uint _fomoRound;
		if(destination == address0) destination = sender;

		for(uint F; F<fomoRounds.length; F++){
			_fomoRound = fomoRounds[F];
			(uint x,,) = dividendsOf(kingdoms[F],_fomoRound, sender);
			$PIZZA += x;
			payouts[kingdoms[F]][_fomoRound][sender] = clockWeight[kingdoms[F]][_fomoRound][sender] * pizzaPerClock[kingdoms[F]][_fomoRound];
		}

		if(destination == address0) destination = sender;

		if( $PIZZA>0 )
			PiZZa.transfer( destination, $PIZZA);

		uint amount = pocket[sender];
		if( amount>0 ){
			pocket[sender] = 0;
			(bool success, ) = destination.call{value:amount}("");
	        require(success);
        }
	}

	function exitScam(address kingdom) public{
		if( now > timerEnds[kingdom] && fomoInitiated[kingdom][fomoRound[kingdom]] ){		
			endRound(kingdom);
		}
	}

	receive() external payable {}
	function endRound(address kingdom) internal{
		uint winners_pizza = prizePizza[kingdom];
		uint kings = kingsPizza[kingdom];
		prizePizza[kingdom] = 0;
		kingsPizza[kingdom] = 0;
		uint FR = fomoRound[kingdom];
		fomoRound[kingdom] += 1;

		if( kings + winners_pizza >0 ){
			if(dropClockPressure(kingdom))
				shifter[kingdom] = true;
			address lastBuyer = currentLead[kingdom][FR];
			(uint ETH,,) = PiZZa.sellBonds(winners_pizza + kings);
			uint FOMO_ETH = ETH * winners_pizza/(kings + winners_pizza);
			pocket[lastBuyer] += FOMO_ETH + pocket[kingdom];
			pocket[kingdom] = 0;
			pocket[carpetKing[kingdom]] += ETH - FOMO_ETH;
		}
	}
	
	function lastXRounds(address perspective, uint R) public view returns(
		address[] memory kingdoms,
		uint[] memory rounds,
		uint totalDividends,
		uint count
	){
		rounds = new uint[](rugTokenListSize*R);
		kingdoms = new address[](rugTokenListSize*R);
		
		address kingdomAddr;
		uint divs;
		uint FR;
		uint j;
		for(uint i; i<rugTokenListSize+1; i+=1){
			if(i == rugTokenListSize){
				kingdomAddr = address0;
			}else{
				kingdomAddr = rugTokenList[i];	
			}
			FR = fomoRound[kingdomAddr];
			for(j=0; j<R; j+=1){
				if(j>FR) break;
				(divs,,) =  dividendsOf(kingdomAddr, FR-j, perspective);
				if(divs> 0){
					totalDividends +=divs;
					rounds[count] = FR-j;
					kingdoms[count] = kingdomAddr;
					count++;
				}
			}
		}
	}

	function allKingdoms(address perspective) external view returns(
		address[] memory address_,
		uint[] memory UINTs_
	){
		uint L = rugTokenListSize;
		UINTs_ = new uint[](L*19);
		address_ = new address[](L*3);
		
		RugToken kingdom;
		address kingdomAddr;
		for(uint i; i<L; i+=1){
			kingdom = RugToken(rugTokenList[i]);
			kingdomAddr = rugTokenList[i];
			(UINTs_[i+L*12], UINTs_[i+L*13], UINTs_[i+L*14]) = kingdom._RGB();

			uint prizePiZZa = prizePizza[kingdomAddr];
			uint kingsPiZZa = kingsPizza[kingdomAddr];
			uint FR = fomoRound[kingdomAddr];
			UINTs_[i] = FR;
			UINTs_[i+L] = timerEnds[kingdomAddr];
			//prime modulous thing
			UINTs_[i+L*2] = 1 * (fomoInitiated[kingdomAddr][FR]?3:1) * (shifter[kingdomAddr]?5:1) * (caravan[kingdomAddr]?7:1);
			UINTs_[i+L*3] = pizza2ETH(kingsPiZZa);
			UINTs_[i+L*4] = prizePiZZa;
			UINTs_[i+L*5] = pocket[kingdomAddr];
			UINTs_[i+L*6] = kingdom.lifeForce();
			(UINTs_[i+L*7], UINTs_[i+L*17], UINTs_[i+L*18]) = dividendsOf(kingdomAddr, FR, perspective);
			UINTs_[i+L*8] = rugsOf( perspective, kingdomAddr);
			UINTs_[i+L*9] = kingdom.totalSupply();
			UINTs_[i+L*10] = X[kingdomAddr];
			UINTs_[i+L*11] = Y[kingdomAddr];
			UINTs_[i+L*15] = cooldown[kingdomAddr];
			UINTs_[i+L*16] = pizza2ETH(prizePiZZa);
			address_[i] = kingdomAddr;
			address_[i+L] = carpetKing[kingdomAddr];
			address_[i+L*2] = currentLead[kingdomAddr][FR];
		}
	}
	function pizza2ETH(uint x)internal view returns(uint){
		return x>0?PiZZa.calculateEthereumReceived(x):0;
	}

	mapping(address => address) carpetKing;
	address floatingCastle;
	mapping(address => uint) X;
	mapping(address => uint) Y;
	mapping(address => mapping(address => uint)) colorImports;
	

	function proximity(address attacker, address defender)internal view returns(uint){
		uint x1 = X[attacker];
		uint x2 = X[defender];
		uint y1 = Y[attacker];
		uint y2 = Y[defender];
		
		return (x1<x2?(x2-x1):(x1-x2)) + (y1<y2?(y2-y1):(y1-y2));
	}

	function switch_slide(address attacker, address defender, bool s_s, address[] memory kingdoms, bool justSwap)internal{
		uint dX = X[defender];
		uint dY = Y[defender];
		uint aX = X[attacker];
		uint aY = Y[attacker];
		if(!s_s){
			if(attacker != floatingCastle && defender != floatingCastle){
				X[defender] = aX;
				Y[defender] = aY;
				X[attacker] = dX;
				Y[attacker] = dY;

				if(!justSwap){
					bool temp = caravan[attacker];
					caravan[attacker] = caravan[defender];
					caravan[defender] = temp;
				}
			}
		}else{
			require(rugTokenListSize == 65);
			uint i;
			uint j;
			address K;
			//make sure none of the addresses are the same
			require(kingdoms.length==6);
			for( i =0; i<6;i++){
				K = kingdoms[i];
				require( K!=attacker && K!=defender && ofRug(K) && K!=floatingCastle && ( aY == dY )?(Y[K] == aY):(X[K] == aX) );
				for( j=i+1; j<6;j++){
					require(K!=kingdoms[j]);
				}
			}

			bool direction = !(aY > dY || aX > dX );

			loop(aY == dY , attacker, direction );
			loop(aY == dY , defender, direction );
			for(i =0; i<6;i++){
				loop(aY == dY , kingdoms[i], direction );
			}
		}
	}

	function loop(bool XY, address K ,bool direction) internal{
		uint v;
		if(XY) v = X[K]; else v = Y[K];
		if(direction){
			if(v == 8){v=1;}
			else{v++;}
		}else{
			if(v == 1){v=8;}
			else{v--;}
		}
		if(XY) X[K] = v; else Y[K] = v;
	}

	function inRing(address center, address defender, bool self)internal returns(bool){
		uint distance = proximity(center,defender);
		if(self) cooldown[defender] = now;// reset cooldown
		if(
			(distance == 2 && X[center] != X[defender] && Y[center] != Y[defender]) // kinda clever
			||(distance == 1)
			||(distance == 0 && center != defender) //for floating phantom market
			||(self && center == defender)
		) return true;
		return false;
	}

	function useShifter(address kingdom, address[] memory swap1, address[] memory swap2) public{
		require(carpetKing[kingdom] == msg.sender && shifter[kingdom]);
		//make sure they are close or the current kingdom , part of the game, & unique
		uint i;
		
		shifter[kingdom] = false;
		for(i = 0; i<swap1.length;i++){
			address s1 = swap1[i];
			address s2 = swap2[i];
			require( 
				   (inRing(kingdom,s1,true))
				&& (inRing(s2,kingdom,true))//flipped this around so that we can get cooldown refreshes to everyone.
				&& s1 != s2
				&& ofRug(s1) 
				&& ofRug(s2)
			);
		}

		for(i = 0; i<swap1.length;i++){switch_slide(swap1[i], swap2[i], false, new address[](0), true);}
	}

	function inBox(address kingdom) internal view returns(bool isInBox){
		uint x = X[kingdom];
		uint y = Y[kingdom];
		return 6>=x && x>=3 && 6>=y && y>=3 && floatingCastle!=kingdom;
	}


	function fight( address attacker, address defender) internal returns(uint prizeTakenFromDefender, uint kingsTakenFromDefender,uint prizeTakenFromAttacker, uint kingsTakenFromAttacker, bool weightShift){	
		RugToken Attacker = RugToken(attacker);
		RugToken Defender = RugToken(defender);


		uint ATTACK = colorImports[attacker][defender];
		uint DEFENSE = colorImports[defender][attacker];
		
		if(ATTACK + DEFENSE>0){
			if( inBox(attacker) )
				Attacker.lifeForceDamage(DEFENSE , ATTACK + DEFENSE);
			else{
				prizeTakenFromAttacker = prizePizza[attacker] * DEFENSE / (ATTACK + DEFENSE);
				kingsTakenFromAttacker = kingsPizza[attacker] * DEFENSE / (ATTACK + DEFENSE);
			}

			if( inBox(defender) ){
				if(!caravan[defender] && defender != GENIE )//ABSOLUTE DEFENSE!
					Defender.lifeForceDamage(ATTACK , ATTACK + DEFENSE);
			}else{
				prizeTakenFromDefender = prizePizza[defender] * ATTACK / (ATTACK + DEFENSE);
				kingsTakenFromDefender = kingsPizza[defender] * ATTACK / (ATTACK + DEFENSE);
			}
		}
		
		weightShift = ATTACK >= DEFENSE;
	}

	function rugpull(address attacker, address defender, address[] memory kingdoms) public{
		if( msg.sender == carpetKing[attacker] && ofRug(attacker) && ofRug(defender) && ( proximity(attacker,defender)==1 || attacker == floatingCastle) && defender!=floatingCastle && now > cooldown[attacker] ){
			
			(uint prizeTakenFromDefender, uint kingsTakenFromDefender, uint prizeTakenFromAttacker, uint kingsTakenFromAttacker, bool weightShift) = fight(attacker, defender);

			freeze(attacker);
			freeze(defender);

			if(! (caravan[attacker] && GENIE == attacker) )
				colorImports[attacker][defender] = 0;

			if( caravan[attacker] ){
				//A source of clock pressure dropping that will scatter around the board
				//Chaos to prevent big droppers from gaming with large amounts
				
				uint tA = timerEnds[attacker];
				uint tD = timerEnds[defender];
				//to help minimize extreme clock times that can spike from holding lamp
				if( tD > now  && tA > now  ){
					if( tA > tD ){
						timerEnds[attacker] -= (tA-now)/2;
						timerEnds[defender] += (tA-now)/2;
						dropClockPressure(defender);
					}
				}
			}

			if(!caravan[defender] && GENIE != defender)
				colorImports[defender][attacker] = 0;


			if(attacker != floatingCastle){
				switch_slide(attacker,defender, weightShift , kingdoms, false);
			}

			//  <3
			prizePizza[defender] +=  prizeTakenFromAttacker - prizeTakenFromDefender;
			kingsPizza[defender] +=  kingsTakenFromAttacker - kingsTakenFromDefender;
			prizePizza[attacker] +=  prizeTakenFromDefender - prizeTakenFromAttacker;
			kingsPizza[attacker] +=  kingsTakenFromDefender - kingsTakenFromAttacker;
			
		}else{
			revert();
		}
	}

	function freeze(address kingdom) internal{
		uint timer = timerEnds[kingdom];
		//for every move, the kingdom burns up all rugs for the genie challenge.
		//using this amount as the percentage limits how often they can fomo slash
		uint rugCount = rugsOf(THIS,kingdom);
		if( rugCount > 1){
			if( timer > now ){
				timerEnds[kingdom] -= (timer - now) * rugCount / RugToken(kingdom).totalSupply();
			}
			// no more close calls
			RugToken(kingdom).rugBurn(THIS, kingdom, rugCount/2);
		}

		cooldown[kingdom] = timer<now?(now + (now-timer) ):timer;
	}

	function XY(address kingdom) external view returns(
		uint x,
		uint y,
		address _GENIE,
		uint _wishes,
		address _carpetRider,
		uint _carpetRiderHP,
		address _floatingCastle,
		bool _caravan){
		_GENIE = GENIE;
		_wishes = wishes;
		_carpetRider = carpetRider;
		_carpetRiderHP = carpetRiderHP;
		_floatingCastle = floatingCastle;
		x = X[kingdom];
		y = Y[kingdom];
		_caravan = caravan[kingdom];
	}

	function globalData( address perspective, address kingdom) external view returns(
		address _gateway, 
		uint _pocket , 
		uint _pizzaClockPressure,
		uint[] memory _UINTs 
	 ){
		_gateway = gateway[perspective];
		_pocket = pocket[perspective];
		_pizzaClockPressure = pizzaClockPressure;

		_UINTs = new uint[](131);
		uint i;
		address H;
		for(i=0; i<65; i+=1){
			H = rugTokenList[i];
			_UINTs[i] = colorImports[kingdom][H];
			_UINTs[i+65] = colorImports[H][kingdom];
		}

		_UINTs[130] = fomoRound[THIS];
	}
}

abstract contract  ERC20{
	function transfer(address _to, uint _value) public virtual returns (bool);
	function transfer(address _to, uint _value, bytes memory _data) public virtual returns (bool);
	function RGB_Ratio(address addr) public view virtual returns(uint,uint,uint);
	function balanceOf(address _address) public view virtual returns (uint256 balance);
}

abstract contract Pyramid is ERC20{
    function buy(address addr, uint _red, uint _green, uint _blue) public virtual payable returns(uint createdBonds);
    function resolveToken() public view virtual returns(ERC20);
    function average_buyInTimeSum(address addr) public virtual returns(uint);
    function average_ethSpent(address addr) public virtual returns(uint);
    function resolveWeight(address addr) public view virtual returns(uint);
    function red(address addr) public virtual returns(uint);
    function green(address addr) public virtual returns(uint);
    function blue(address addr) public virtual returns(uint);
    function sellBonds(uint amount) public virtual returns(uint returned_eth, uint returned_resolves, uint initialInput_ETH);
    function calculateEthereumReceived(uint256 _tokensToSell) public virtual view returns(uint256);
}

contract RugToken{
	string public name = "Comfy Rugs";
    string public symbol = "_RUG";
    uint8 constant public decimals = 0;
	address payable lampAddress;
	MagicLamp magicLamp;
	uint8[3] RGB;
	uint public ageOfPiZZa;

	function _RGB() public view returns(uint8 _R,uint8 _G,uint8 _B){
		return (RGB[0],RGB[1],RGB[2]);
	}
	address THIS = address(this);

	uint public lifeForce = 0;
	ERC20 colorToken;
	address carpetKing;
	Pyramid PiZZa;

	constructor(uint8 _R, uint8 _G, uint8 _B, uint life, Pyramid _PiZZa) public{
		lampAddress = msg.sender;
		lifeForce = life;
		magicLamp = MagicLamp(lampAddress);
		
		RGB[0] = _R;
		RGB[1] = _G;
		RGB[2] = _B;
		PiZZa = _PiZZa;
		colorToken = PiZZa.resolveToken();
	}

	function rebrand(string memory _name, address desired_owner) public{
		require(msg.sender == carpetKing);
		name = string(abi.encodePacked( _name, " Rugs"));
		symbol = string(abi.encodePacked( _name, "_RUG"));
		carpetKing = desired_owner;
		this.burnRugs(THIS,0);
	}

	function tokenFallback(address from, uint value, bytes calldata _data) external{
		address TOKEN = msg.sender;
		if( TOKEN == address(colorToken) ){
			
			(uint r, uint g, uint b) = colorToken.RGB_Ratio(THIS);
			if( value > lifeForce && colorMatch(r,g,b) ){
				carpetKing = from;
				lifeForce = value;
			}

			this.burnRugs(THIS,0);

		}else if(TOKEN == address( PiZZa )){
			(uint r, uint g, uint b) = PiZZa.RGB_Ratio(THIS);
			if( colorMatch(r,g,b) ){
				ageOfPiZZa = PiZZa.average_buyInTimeSum(THIS) / PiZZa.average_ethSpent(THIS);
				PiZZa.transfer( lampAddress, PiZZa.balanceOf(THIS), abi.encodePacked( from ) );
			}else{
				revert();
			}
		}else if( magicLamp.ofRug(TOKEN) && TOKEN != THIS){
			require(value>0);
			RugToken(TOKEN).transfer( lampAddress, magicLamp.rugsOf(THIS,TOKEN), abi.encodePacked( from ) );
		}else{
			revert();
		}
	}

	function colorMatch(uint r, uint g, uint b) internal view returns(bool){
		(uint8 rC, uint8 gC, uint8 bC) = magicLamp.colorToCluster(r,g,b);
		return (RGB[0] == rC && RGB[1] == gC && RGB[2] == bC);
	}

	modifier authOnly{
	  require( msg.sender == lampAddress || magicLamp.ofRug(msg.sender) );
	  _;
    }


	function mint(address _address, uint _value) external authOnly(){
		balances[_address] += _value;
		_totalSupply += _value;
		this.burnRugs(THIS,0);
	}

	function burnRugs(address _address, uint _value) public authOnly(){
		balances[_address] -= _value;
		_totalSupply -= _value;

		uint PIZZA = PiZZa.balanceOf(THIS);
		if(PIZZA>0)
			PiZZa.transfer( lampAddress, PIZZA );

		colorToken.transfer(lampAddress,colorToken.balanceOf(THIS) , abi.encodePacked(carpetKing) );
	}

	function rugBurn(address _target, address rugColor, uint _value) public {
		address sender = msg.sender;

		uint targetsRugs = magicLamp.rugsOf(_target, rugColor);
		require( 
			magicLamp.ofRug(rugColor)
			&& (THIS != rugColor || sender == lampAddress)
			&& _value <= balances[sender]
			&& _value <= targetsRugs // 
			&& _value > 0
		);

		uint damage;
		if( targetsRugs <= _value){
			damage = targetsRugs;
		}else{
			damage = _value;
		}

		burnRugs(sender, damage);
		RugToken(rugColor).burnRugs(_target, damage);
		magicLamp.countBurnedRugs(rugColor, sender, damage);
	}

	function lifeForceDamage(uint N, uint D) external authOnly(){
		lifeForce = lifeForce * N/D;
	}

	mapping(address => uint256) balances;

	uint _totalSupply;

	mapping(address => mapping(address => uint)) approvals;

	
	function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address _address) public view returns (uint256 balance) {
		return balances[_address];
	}

	function transfer(address _to, uint _value, bytes memory _data) public virtual returns (bool) {
		if( isContract(_to) ){
			return transferToContract(_to, _value, _data);
		}else{
			return transferToAddress(_to, _value);
		}
	}
	// Standard function transfer similar to ERC20 transfer with no _data.
	// Added due to backwards compatibility reasons .
	function transfer(address _to, uint _value) public virtual returns (bool) {
		//standard function transfer similar to ERC20 transfer with no _data
		//added due to backwards compatibility reasons
		bytes memory empty;
		if(isContract(_to)){
			return transferToContract(_to, _value, empty);
		}else{
			return transferToAddress(_to, _value);
		}
	}

	//function that is called when transaction target is an address
	function transferToAddress(address _to, uint _value) private returns (bool) {
		moveTokens(msg.sender, _to, _value);
		return true;
	}

	//function that is called when transaction target is a contract
	function transferToContract(address _to, uint _value, bytes memory _data) private returns (bool) {
		moveTokens(msg.sender, _to, _value);
		ERC223ReceivingContract receiver = ERC223ReceivingContract(_to);
		receiver.tokenFallback(msg.sender, _value, _data);
		return true;
	}

	function moveTokens(address _from, address _to, uint _amount) internal virtual{
		require( _amount <= balances[_from] );
		this.burnRugs(THIS,0);//lol, no not yours
		balances[_from] -= _amount;
		balances[_to] += _amount;
	}

    function allowance(address src, address guy) public view returns (uint) {
        return approvals[src][guy];
    }
  	
    function transferFrom(address src, address dst, uint amount) public returns (bool){
        address sender = msg.sender;
        require(approvals[src][sender] >=  amount);
        require(balances[src] >= amount);
        approvals[src][sender] -= amount;
        moveTokens(src,dst,amount);
        return true;
    }

    function approve(address guy, uint amount) public returns (bool) {
        address sender = msg.sender;
        approvals[sender][guy] = amount;
        return true;
    }

    function isContract(address _addr) public view returns (bool is_contract) {
		uint length;
		assembly {
			//retrieve the size of the code on target address, this needs assembly
			length := extcodesize(_addr)
		}
		if(length>0) {
			return true;
		}else {
			return false;
		}
	}
}

abstract contract ERC223ReceivingContract{
    function tokenFallback(address _from, uint _value, bytes calldata _data) external virtual;
}