pragma solidity ^0.4.18;
//
// AddressWars
// (http://beta.addresswars.io/)
// Public Beta v1.1
//
//
// .d8888b. .d8888b.
// d88P Y88b d88P Y88b
// 888 888 888 888
// 888 888888 888 888 888.d8888b 888 888888 888
// 888 888`Y8bd8P' 888 88888K 888 888`Y8bd8P'
// 888 888 X88K Y88 88P"Y8888b. 888 888 X88K
// Y88b d88P.d8""8b. Y8bd8P X88 Y88b d88P.d8""8b.
// "Y8888P" 888 888 Y88P 88888P' "Y8888P" 888 888
//
//
// ******************************
// Welcome to AddressWars Beta!
// ******************************
//
// This contract is currently in a state of being tested and bug hunted,
// as this is the beta, there will be no fees for enlisting or wagering.
// This will encourage anyone to come and try out AddressWars for free
// before deciding to play the live version (when released) as well as
// making it so that the contract is tested to the fullest ability before
// the live version is deployed. The website is currently under development
// and will be continually improved as time goes on, once the live version
// is deployed, you can access it's contract and data through the root url
// (https://addresswars.io/) and there will always be a copy of the website
// on a subdomain that you can visit in order to view and interact with this
// contract at any time in the future.
//
// This contract is pushing the limits of the current Ethereum blockchain as
// there are quite a lot of variables that it needs to keep track of as well
// as being able to handle the statistical generation of key numbers. As a
// result, the estimated gas cost to deploy this contract is 7.5M whereas
// the current block gas limit is only 8M so this contract will take up
// almost a whole block! Another problem with complex contracts is the fact
// that there is a 16 local variable limit per function and in a few cases,
// the functions needed access to a lot more than that so a lot of filtering
// functions have been developed to handle the calculation internally then
// simply return the useful parts.
//
//
// **************************
// How to play AddressWars!
// **************************
//
// Enlisting
// In order to start playing AddressWars, you must first have an Ethereum
// wallet address which you can issue transactions from but only non-contract
// addresses (ie addresses where you can issue a transaction directly) can play.
// From here, you can simply call the enlist() function and send the relevant
// fee (in the beta it's 0 ETH, for the live it will be 0.01 ETH). After the
// transaction succeeds, you will have your very own, randomly generated
// address card that you can now put up for wager or use to attempt to claim
// other address cards with!
//
// Wagering
// You can choose to wager any card you own assuming you are not already
// wagering a copy of that card. For your own address, you can provide a
// maximum of 10 other copies to any other addresses (by either transferring
// or wagering), once 10 copies of your own address are circulating, you will
// no longer be able to transfer or wager your own address (although you can
// still use it to claim other addresses). It is important to note that there
// is no way to 'destroy' a copy of a card in circulation and your own address
// card cannot be transferred back to you (and you can't attempt to claim your
// own address card).
// To wager a card, simply call the wagerCardForAmount() function and send
// the relevant fee (in the beta it's 0 ETH, for the live it will be 0.005 ETH)
// as well as the address of the card you wish to wager and the amount you are
// willing to wager it for (in wei). From this point, any other address can
// attempt to claim the address card you listed but the card that will be put up
// for claim will be the one with the lowest claim price (this may not be yours
// at the time but as soon as a successful claim happens and you are the next
// cheapest price, the next time someone attempts to claim that address, you
// will receive the wager and your card may be claimed and taken from you).
//
// Claiming
// Assuming your address has already enlisted, you are free to attempt to
// claim any card that has a current wager on it. You can only store up to
// 8 unique card addresses (your own address + 7 others) in your inventory
// and you cannot claim if you are already at this limit (unless you already own
// that card). You can claim as many copies of a card you own as you like but you
// can only get rid of them by wagering off one at a time. As mentioned above,
// the claim contender will be the owner of the cheapest claim wager. You cannot
// claim a card if you are the current claim contender or if the card address is
// the same as your address (ie you enlisted and got that card).
// To attempt to claim, you need to first assemble an army of 3 address cards
// (these cards don't have to be unique but you do have to own them) and send the
// current cheapest wager price to the attemptToClaimCard() function. This function
// will do all of the calculation work for you and determine if you managed to claim
// the card or not. The first thing that happens is the contract randomly picks
// your claim contenders cards ensuring that at least one of the cards is the card
// address you are attempting to claim and the rest are from their inventory.
// After this point, all of the complex maths happens and the final attack
// and defence numbers will be calculated based on all of the cards types,
// modifiers and the base attack and defence stats.
// From here it's simply a matter of determining how many hits got through
// on both claimer and claim contender, it's calculated as follows;
// opponentsHits = yourCard[attack] - opponentsCard[defence]
// ^ will be 0 if opponentsCard[defence] > yourCard[attack]
// This is totalled up for both the claimer and the claim contender and the
// one with the least amount of hits wins!
//
// Claim Outcomes
// There are 3 situations that can result from a claim attempt;
// 1. The total hits for both the claimer and the claim contender are equal
// - this means that you have drawn with your opponent, the wager will
// then be distributed;
// 98% -> the claimer (you get most of the wager back)
// 2% -> the dev
// 2. The claimer has more hits than the claim contender
// - this means that you have lost against your opponent as they ended
// up taking less hits than you, the wager will then be distributed;
// 98% -> the claim contender (they get most of the wager)
// 2% -> the dev
// 3. The claimer has less hits than the claim contender
// - this means that you have succeeded in claiming the card and hence
// that card address will be transferred from the claim contender
// to the claimer. in this case, both claimer and claim contender
// receive a portion of the wager as follow;
// 50% -> the claimer (you get half of the wager back)
// 48% -> the claim contender (they get about half of the wager)
// 2% -> the dev
//
// Transferring
// You are free to transfer any card you own to another address that has
// already enlisted. Upon transfer, only one copy of the card will be removed
// from your inventory so if you have multiple copies of that card, it will
// not be completely removed from your inventory. If you only had one copy
// though, that address will be removed from your inventory and you will
// be able to claim/receive another address in its place.
// There are some restrictions when transferring a card;
// 1. you cannot be currently wagering the card
// 2. the address you are transferring to must already be enlisted
// 3. the address you are transferring the card to must have less than
// 8 unique cards already (or they must already own the card)
// 4. you cannot transfer a card back to it's original address
// 5. if you are gifting your own address card, the claim limit will apply
// and if 10 copies already exist, you will not be able to gift your card.
//
// Withdrawing
// All ETH transferred to the contract will stay in there until an
// address wishes to withdraw from their account. Balances are tracked
// per address and you can either withdraw an amount (assuming you have
// a balance higher than that amount) or you can just withdraw it all.
// For both of these cases there will be no fees associated with withdrawing
// from the contract and after you choose to withdraw, your balance will
// update accordingly.
//
//
// Have fun and good luck claiming!
//
contract AddressWarsBeta {
//////////////////////////////////////////////////////////////////////
// Constants
// dev
address public dev;
uint256 constant devTax = 2; // 2% of all wagers
// fees
// in the live version the;
// enlistingFee will be 0.01 ether and the
// wageringFee will be 0.005 ether
uint256 constant enlistingFee = 0;
uint256 constant wageringFee = 0;
// limits
// the claim limit represents how many times an address can
// wager/trasnfer their own address. in this case the limit
// is set to 10 which means there can only ever be 10 other
// copies of your address out there. once you have wagered
// all 10 copies, you will no longer be able to wager your
// own address card (although you can still use it in play).
uint256 constant CLAIM_LIMIT = 10;
// this will limit how many unique addresses you can own at
// one time. you can own multiple copies of a unique address
// but you can only own a total of 8 unique addresses (your
// own address + 7 others) at a time. you can choose to wager
// any address but if you wager one, the current claim price is the
// lowest price offered from all owners. upon a successful claim,
// one copy will transfer from your inventory and if you have no
// copies remaining, it will remove that address card and you will
// have another free slot.
uint256 constant MAX_UNIQUE_CARDS_PER_ADDRESS = 8;
//////////////////////////////////////////////////////////////////////
// Statistical Variables
// this is used to calculate all of the statistics and random choices
// within AddressWars
// see the shuffleSeed() and querySeed() methods for more information.
uint256 private _seed;
// the type will determine a cards bonus numbers;
// normal cards do not get any type advantage bonuses
// fire gets 1.25x att and def when versing nature
// water gets 1.25x att and def when versing fire
// nature gets 1.25x att and def when versing water
// *type advantages are applied after all modifiers
// that use addition are calculated
enum TYPE { NORMAL, FIRE, WATER, NATURE }
uint256[] private typeChances = [ 6, 7, 7, 7 ];
uint256 constant typeSum = 27;
// the modifier will act like a bonus for your card(s)
// NONE: no bonus will be applied
// ALL_: if all cards are of the same type, they all get
// bonus att/def/att+def numbers
// V_: if a versing card is of a certain type, your card
// will get bonus att/def numbers
// V_SWAP: this will swap the versing cards att and def
// numbers after they've been modified by any
// other active modifiers
// R_V: your card resists the type advantages of the versing card,
// normal type cards cannot receive this modifier
// A_I: your cards type advantage increases from 1.25x to 1.5x,
// normal type cards cannot receive this modifier
enum MODIFIER {
NONE,
ALL_ATT, ALL_DEF, ALL_ATT_DEF,
V_ATT, V_DEF,
V_SWAP,
R_V,
A_I
}
uint256[] private modifierChances = [
55,
5, 6, 1,
12, 14,
3,
7,
4
];
uint256 constant modifierSum = 107;
// below are the chances for the bonus stats of the modifiers,
// the seed will first choose a value between 0 and the sum, it will
// then cumulatively count up until it reaches the index with the
// matched roll
// for example;
// if your data was = [ 2, 3, 4, 2, 1 ], your cumulative total is 12,
// from there a number will be rolled and it will add up all the values
// until the cumulative total is greater than the number rolled
// if we rolled a 9, 2(0) + 3(1) + 4(2) + 2(3) = 11 > 9 so the index
// you matched in this case would be 3
// the final value will be;
// bonusMinimum + indexOf(cumulativeRoll)
uint256 constant cardBonusMinimum = 1;
uint256[] private modifierAttBonusChances = [ 2, 5, 8, 7, 3, 2, 1, 1 ]; // range: 1 - 8
uint256 constant modifierAttBonusSum = 29;
uint256[] private modifierDefBonusChances = [ 2, 3, 6, 8, 6, 5, 3, 2, 1, 1 ]; // range: 1 - 10
uint256 constant modifierDefBonusSum = 37;
// below are the distribution of the attack and defence numbers,
// in general, the attack average should be slightly higher than the
// defence average and defence should have a wider spread of values
// compared to attack which should be a tighter set of numbers
// the final value will be;
// cardMinimum + indexOf(cumulativeRoll)
uint256 constant cardAttackMinimum = 10;
uint256[] private cardAttackChances = [ 2, 2, 3, 5, 8, 9, 15, 17, 13, 11, 6, 5, 3, 2, 1, 1 ]; // range: 10 - 25
uint256 constant cardAttackSum = 103;
uint256 constant cardDefenceMinimum = 5;
uint256[] private cardDefenceChances = [ 1, 1, 2, 3, 5, 6, 11, 15, 19, 14, 12, 11, 9, 8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 1, 1, 1 ]; // range: 5 - 30
uint256 constant cardDefenceSum = 153;
//////////////////////////////////////////////////////////////////////
// Registry Variables
// overall address card tracking
mapping (address => bool) _exists;
mapping (address => uint256) _indexOf;
mapping (address => address[]) _ownersOf;
mapping (address => uint256[]) _ownersClaimPriceOf;
struct AddressCard {
address _cardAddress;
uint8 _cardType;
uint8 _cardModifier;
uint8 _modifierPrimarayVal;
uint8 _modifierSecondaryVal;
uint8 _attack;
uint8 _defence;
uint8 _claimed;
uint8 _forClaim;
uint256 _lowestPrice;
address _claimContender;
}
AddressCard[] private _addressCards;
// owner and balance tracking
mapping (address => uint256) _balanceOf;
mapping (address => address[]) _cardsOf;
//////////////////////////////////////////////////////////////////////
// Events
event AddressDidEnlist(
address enlistedAddress);
event AddressCardWasWagered(
address addressCard,
address owner,
uint256 wagerAmount);
event AddressCardWagerWasCancelled(
address addressCard,
address owner);
event AddressCardWasTransferred(
address addressCard,
address fromAddress,
address toAddress);
event ClaimAttempt(
bool wasSuccessful,
address addressCard,
address claimer,
address claimContender,
address[3] claimerChoices,
address[3] claimContenderChoices,
uint256[3][2] allFinalAttackValues,
uint256[3][2] allFinalDefenceValues);
//////////////////////////////////////////////////////////////////////
// Main Functions
// start up the contract!
function AddressWarsBeta() public {
// set our dev
dev = msg.sender;
// now use the dev address as the initial seed mix
shuffleSeed(uint256(dev));
}
// any non-contract address can call this function and begin playing AddressWars!
// please note that as there are a lot of write to storage operations, this function
// will be quite expensive in terms of gas so keep that in mind when sending your
// transaction to the network!
// 350k gas should be enough to handle all of the storage operations but MetaMask
// will give a good estimate when you initialize the transaction
// in order to enlist in AddressWars, you must first pay the enlistingFee (free for beta!)
function enlist() public payable {
require(cardAddressExists(msg.sender) == false);
require(msg.value == enlistingFee);
require(msg.sender == tx.origin); // this prevents contracts from enlisting,
// only normal addresses (ie ones that can send a request) can play AddressWars.
// first shuffle the main seed with the sender address as input
uint256 tmpSeed = tmpShuffleSeed(_seed, uint256(msg.sender));
uint256 tmpModulus;
// from this point on, tmpSeed will shuffle every time tmpQuerySeed()
// is called. it is used recursively so it will mutate upon each
// call of that function and finally at the end we will update
// the overall seed to save on gas fees
// now we can query the different attributes of the card
// first lets determine the card type
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, typeSum);
uint256 cardType = cumulativeIndexOf(typeChances, tmpModulus);
// now to get the modifier
// special logic to handle normal type cards
uint256 adjustedModifierSum = modifierSum;
if (cardType == uint256(TYPE.NORMAL)) {
// normal cards cannot have the advantage increase modifier (the last in the array)
adjustedModifierSum -= modifierChances[modifierChances.length - 1];
// normal cards cannot have the resistance versing modifier (second last in the array)
adjustedModifierSum -= modifierChances[modifierChances.length - 2];
}
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, adjustedModifierSum);
uint256 cardModifier = cumulativeIndexOf(modifierChances, tmpModulus);
// now we need to find our attack and defence values
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, cardAttackSum);
uint256 cardAttack = cardAttackMinimum + cumulativeIndexOf(cardAttackChances, tmpModulus);
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, cardDefenceSum);
uint256 cardDefence = cardDefenceMinimum + cumulativeIndexOf(cardDefenceChances, tmpModulus);
// finally handle our modifier values
uint256 primaryModifierVal = 0;
uint256 secondaryModifierVal = 0;
uint256 bonusAttackPenalty = 0;
uint256 bonusDefencePenalty = 0;
// handle the logic of our modifiers
if (cardModifier == uint256(MODIFIER.ALL_ATT)) { // all of the same type attack bonus
// the primary modifier value will hold our attack bonus
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, modifierAttBonusSum);
primaryModifierVal = cardBonusMinimum + cumulativeIndexOf(modifierAttBonusChances, tmpModulus);
// now for the attack penalty
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, modifierAttBonusSum);
bonusAttackPenalty = cardBonusMinimum + cumulativeIndexOf(modifierAttBonusChances, tmpModulus);
// penalty is doubled
bonusAttackPenalty *= 2;
} else if (cardModifier == uint256(MODIFIER.ALL_DEF)) { // all of the same type defence bonus
// the primary modifier value will hold our defence bonus
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, modifierDefBonusSum);
primaryModifierVal = cardBonusMinimum + cumulativeIndexOf(modifierDefBonusChances, tmpModulus);
// now for the defence penalty
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, modifierDefBonusSum);
bonusDefencePenalty = cardBonusMinimum + cumulativeIndexOf(modifierDefBonusChances, tmpModulus);
// penalty is doubled
bonusDefencePenalty *= 2;
} else if (cardModifier == uint256(MODIFIER.ALL_ATT_DEF)) { // all of the same type attack and defence bonus
// the primary modifier value will hold our attack bonus
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, modifierAttBonusSum);
primaryModifierVal = cardBonusMinimum + cumulativeIndexOf(modifierAttBonusChances, tmpModulus);
// now for the attack penalty
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, modifierAttBonusSum);
bonusAttackPenalty = cardBonusMinimum + cumulativeIndexOf(modifierAttBonusChances, tmpModulus);
// penalty is doubled
bonusAttackPenalty *= 2;
// the secondary modifier value will hold our defence bonus
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, modifierDefBonusSum);
secondaryModifierVal = cardBonusMinimum + cumulativeIndexOf(modifierDefBonusChances, tmpModulus);
// now for the defence penalty
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, modifierDefBonusSum);
bonusDefencePenalty = cardBonusMinimum + cumulativeIndexOf(modifierDefBonusChances, tmpModulus);
// penalty is doubled
bonusDefencePenalty *= 2;
} else if (cardModifier == uint256(MODIFIER.V_ATT)) { // versing a certain type attack bonus
// the primary modifier value will hold type we need to verse in order to get our bonus
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, typeSum);
primaryModifierVal = cumulativeIndexOf(typeChances, tmpModulus);
// the secondary modifier value will hold our attack bonus
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, modifierAttBonusSum);
secondaryModifierVal = cardBonusMinimum + cumulativeIndexOf(modifierAttBonusChances, tmpModulus);
// now for the attack penalty
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, modifierAttBonusSum);
bonusAttackPenalty = cardBonusMinimum + cumulativeIndexOf(modifierAttBonusChances, tmpModulus);
} else if (cardModifier == uint256(MODIFIER.V_DEF)) { // versing a certain type defence bonus
// the primary modifier value will hold type we need to verse in order to get our bonus
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, typeSum);
primaryModifierVal = cumulativeIndexOf(typeChances, tmpModulus);
// the secondary modifier value will hold our defence bonus
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, modifierDefBonusSum);
secondaryModifierVal = cardBonusMinimum + cumulativeIndexOf(modifierDefBonusChances, tmpModulus);
// now for the defence penalty
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, modifierDefBonusSum);
bonusDefencePenalty = cardBonusMinimum + cumulativeIndexOf(modifierDefBonusChances, tmpModulus);
}
// now apply the penalties
if (bonusAttackPenalty >= cardAttack) {
cardAttack = 0;
} else {
cardAttack -= bonusAttackPenalty;
}
if (bonusDefencePenalty >= cardDefence) {
cardDefence = 0;
} else {
cardDefence -= bonusDefencePenalty;
}
// now to add it to the registry
_exists[msg.sender] = true;
_indexOf[msg.sender] = uint256(_addressCards.length);
_ownersOf[msg.sender] = [ msg.sender ];
_ownersClaimPriceOf[msg.sender] = [ uint256(0) ];
_addressCards.push(AddressCard({
_cardAddress: msg.sender,
_cardType: uint8(cardType),
_cardModifier: uint8(cardModifier),
_modifierPrimarayVal: uint8(primaryModifierVal),
_modifierSecondaryVal: uint8(secondaryModifierVal),
_attack: uint8(cardAttack),
_defence: uint8(cardDefence),
_claimed: uint8(0),
_forClaim: uint8(0),
_lowestPrice: uint256(0),
_claimContender: address(0)
}));
// ...and now start your own collection!
_cardsOf[msg.sender] = [ msg.sender ];
// dev receives the enlisting fee
_balanceOf[dev] = SafeMath.add(_balanceOf[dev], enlistingFee);
// finally we need to update the main seed and as we initially started with
// the current main seed, tmpSeed will be the current representation of the seed
_seed = tmpSeed;
// now that we're done, it's time to log the event
AddressDidEnlist(msg.sender);
}
// this is where you can wager one of your addresses for a certain amount.
// any other player can then attempt to claim your address off you, if the
// address is your own address, you will simply give them a copy (limited to 10
// total copies) but otherwise the player will take that address off you if they
// are successful.
// here's what can happen when you wager;
// 1. if an opponent is successful in claiming your card, they will receive 50%
// of the wager amount back, the dev gets 2% and you get 48%
// 2. if an opponent is unsuccessful in claiming your card, you will receive
// 98% of the wager amount and the dev will get 2%
// 3. if an opponent is draws with you when claiming your card, they will receive
// 98% of the wager amount back and the dev will get 2%
// your wager will remain available for anyone to claim up until either you cancel
// the wager or an opponent is successful in claiming your card
// in order to wager in AddressWars, you must first pay the wageringFee (free for beta!)
function wagerCardForAmount(address cardAddress, uint256 amount) public payable {
require(amount > 0);
require(cardAddressExists(msg.sender));
require(msg.value == wageringFee);
uint256 firstMatchedIndex;
bool isAlreadyWagered;
(firstMatchedIndex, isAlreadyWagered, , , ) = getOwnerOfCardsCheapestWager(msg.sender, cardAddress);
// calling the above method will automatically reinforce the check that the cardAddress exists
// as well as the sender actually owning the card
// we cannot wager a card if we are already wagering it
require(isAlreadyWagered == false);
// double check to make sure the card is actually owned by the sender
require(msg.sender == _ownersOf[cardAddress][firstMatchedIndex]);
AddressCard memory addressCardForWager = _addressCards[_indexOf[cardAddress]];
if (msg.sender == cardAddress) {
// we need to enforce the claim limit if you are the initial owner
require(addressCardForWager._claimed < CLAIM_LIMIT);
}
// now write the new data
_ownersClaimPriceOf[cardAddress][firstMatchedIndex] = amount;
// now update our statistics
updateCardStatistics(cardAddress);
// dev receives the wagering fee
_balanceOf[dev] = SafeMath.add(_balanceOf[dev], wageringFee);
// now that we're done, it's time to log the event
AddressCardWasWagered(cardAddress, msg.sender, amount);
}
function cancelWagerOfCard(address cardAddress) public {
require(cardAddressExists(msg.sender));
uint256 firstMatchedIndex;
bool isAlreadyWagered;
(firstMatchedIndex, isAlreadyWagered, , , ) = getOwnerOfCardsCheapestWager(msg.sender, cardAddress);
// calling the above method will automatically reinforce the check that the cardAddress exists
// as well as the owner actually owning the card
// we can only cancel a wager if there already is one
require(isAlreadyWagered);
// double check to make sure the card is actually owned by the sender
require(msg.sender == _ownersOf[cardAddress][firstMatchedIndex]);
// now write the new data
_ownersClaimPriceOf[cardAddress][firstMatchedIndex] = 0;
// now update our statistics
updateCardStatistics(cardAddress);
// now that we're done, it's time to log the event
AddressCardWagerWasCancelled(cardAddress, msg.sender);
}
// this is the main battle function of the contract, it takes the card address you
// wish to claim as well as your card choices as input. a lot of complex calculations
// happen within this function and in the end, a result will be determined on whether
// you won the claim or not. at the end, an event will be logged with all of the information
// about what happened in the battle including the final result, the contenders,
// the card choices (yours and your opponenets) as well as the final attack and defence numbers.
// this function will revert if the msg.value does not match the current minimum claim value
// of the card address you are attempting to claim.
function attemptToClaimCard(address cardAddress, address[3] choices) public payable {
// a lot of the functionality of attemptToClaimCard() is calculated in other methods as
// there is only a 16 local variable limit per method and we need a lot more than that
// see ownerCanClaimCard() below, this ensures we can actually claim the card we are after
// by running through various requirement checks
address claimContender;
uint256 claimContenderIndex;
(claimContender, claimContenderIndex) = ownerCanClaimCard(msg.sender, cardAddress, choices, msg.value);
address[3] memory opponentCardChoices = generateCardsFromClaimForOpponent(cardAddress, claimContender);
uint256[3][2] memory allFinalAttackFigures;
uint256[3][2] memory allFinalDefenceFigures;
(allFinalAttackFigures, allFinalDefenceFigures) = calculateAdjustedFiguresForBattle(choices, opponentCardChoices);
// after this point we have all of the modified attack and defence figures
// in the arrays above. the way the winner is determined is by counting
// how many attack points get through in total for each card, this is
// calculated by simply doing;
// opponentsHits = yourCard[attack] - opponentsCard[defence]
// if the defence of the opposing card is greater than the attack value,
// no hits will be taken.
// at the end, all hits are added up and the winner is the one with
// the least total amount of hits, if it is a draw, the wager will be
// returned to the sender (minus the dev fee)
uint256[2] memory totalHits = [ uint256(0), uint256(0) ];
for (uint256 i = 0; i < 3; i++) {
// start with the opponent attack to you
totalHits[0] += (allFinalAttackFigures[1][i] > allFinalDefenceFigures[0][i] ? allFinalAttackFigures[1][i] - allFinalDefenceFigures[0][i] : 0);
// then your attack to the opponent
totalHits[1] += (allFinalAttackFigures[0][i] > allFinalDefenceFigures[1][i] ? allFinalAttackFigures[0][i] - allFinalDefenceFigures[1][i] : 0);
}
// before we process the outcome, we should log the event.
// order is important here as we should log a successful
// claim attempt then a transfer (if that's what happens)
// instead of the other way around
ClaimAttempt(
totalHits[0] < totalHits[1], // it was successful if we had less hits than the opponent
cardAddress,
msg.sender,
claimContender,
choices,
opponentCardChoices,
allFinalAttackFigures,
allFinalDefenceFigures
);
// handle the outcomes
uint256 tmpAmount;
if (totalHits[0] == totalHits[1]) { // we have a draw
// hand out the dev tax
tmpAmount = SafeMath.div(SafeMath.mul(msg.value, devTax), 100); // 2%
_balanceOf[dev] = SafeMath.add(_balanceOf[dev], tmpAmount);
// now we return the rest to the sender
_balanceOf[msg.sender] = SafeMath.add(_balanceOf[msg.sender], SafeMath.sub(msg.value, tmpAmount)); // 98%
} else if (totalHits[0] > totalHits[1]) { // we have more hits so we were unsuccessful
// hand out the dev tax
tmpAmount = SafeMath.div(SafeMath.mul(msg.value, devTax), 100); // 2%
_balanceOf[dev] = SafeMath.add(_balanceOf[dev], tmpAmount);
// now we give the rest to the claim contender
_balanceOf[claimContender] = SafeMath.add(_balanceOf[claimContender], SafeMath.sub(msg.value, tmpAmount)); // 98%
} else { // this means we have less hits than the opponent so we were successful in our claim!
// hand out the dev tax
tmpAmount = SafeMath.div(SafeMath.mul(msg.value, devTax), 100); // 2%
_balanceOf[dev] = SafeMath.add(_balanceOf[dev], tmpAmount);
// return half to the sender
_balanceOf[msg.sender] = SafeMath.add(_balanceOf[msg.sender], SafeMath.div(msg.value, 2)); // 50%
// and now the remainder goes to the claim contender
_balanceOf[claimContender] = SafeMath.add(_balanceOf[claimContender], SafeMath.sub(SafeMath.div(msg.value, 2), tmpAmount)); // 48%
// finally transfer the ownership of the card from the claim contender to the sender but
// first we need to make sure to cancel the wager
_ownersClaimPriceOf[cardAddress][claimContenderIndex] = 0;
transferCard(cardAddress, claimContender, msg.sender);
// now update our statistics
updateCardStatistics(cardAddress);
}
}
function transferCardTo(address cardAddress, address toAddress) public {
// you can view this internal method below for more details.
// all of the requirements around transferring a card are
// tested within the transferCard() method.
// you are free to gift your own address card to anyone
// (assuming there are less than 10 copies circulating).
transferCard(cardAddress, msg.sender, toAddress);
}
//////////////////////////////////////////////////////////////////////
// Wallet Functions
function withdrawAmount(uint256 amount) public {
require(amount > 0);
address sender = msg.sender;
uint256 balance = _balanceOf[sender];
require(amount <= balance);
// transfer and update the balances
_balanceOf[sender] = SafeMath.sub(_balanceOf[sender], amount);
sender.transfer(amount);
}
function withdrawAll() public {
address sender = msg.sender;
uint256 balance = _balanceOf[sender];
require(balance > 0);
// transfer and update the balances
_balanceOf[sender] = 0;
sender.transfer(balance);
}
function getBalanceOfSender() public view returns (uint256) {
return _balanceOf[msg.sender];
}
//////////////////////////////////////////////////////////////////////
// Helper Functions
function tmpShuffleSeed(uint256 tmpSeed, uint256 mix) public view returns (uint256) {
// really mix it up!
uint256 newTmpSeed = tmpSeed;
uint256 currentTime = now;
uint256 timeMix = currentTime + mix;
// in this instance, overflow is ok as we are just shuffling around the bits
// first lets square the seed
newTmpSeed *= newTmpSeed;
// now add our time and mix
newTmpSeed += timeMix;
// multiply by the time
newTmpSeed *= currentTime;
// now add our mix
newTmpSeed += mix;
// and finally multiply by the time and mix
newTmpSeed *= timeMix;
return newTmpSeed;
}
function shuffleSeed(uint256 mix) private {
// set our seed based on our last seed
_seed = tmpShuffleSeed(_seed, mix);
}
function tmpQuerySeed(uint256 tmpSeed, uint256 modulus) public view returns (uint256 tmpShuffledSeed, uint256 result) {
require(modulus > 0);
// get our answer
uint256 response = tmpSeed % modulus;
// now we want to re-mix our seed based off our response
uint256 mix = response + 1; // non-zero
mix *= modulus;
mix += response;
mix *= modulus;
// now return it
return (tmpShuffleSeed(tmpSeed, mix), response);
}
function querySeed(uint256 modulus) private returns (uint256) {
require(modulus > 0);
uint256 tmpSeed;
uint256 response;
(tmpSeed, response) = tmpQuerySeed(_seed, modulus);
// tmpSeed will now represent the suffled version of our last seed
_seed = tmpSeed;
// now return it
return response;
}
function cumulativeIndexOf(uint256[] array, uint256 target) private pure returns (uint256) {
bool hasFound = false;
uint256 index;
uint256 cumulativeTotal = 0;
for (uint256 i = 0; i < array.length; i++) {
cumulativeTotal += array[i];
if (cumulativeTotal > target) {
hasFound = true;
index = i;
break;
}
}
require(hasFound);
return index;
}
function cardAddressExists(address cardAddress) public view returns (bool) {
return _exists[cardAddress];
}
function indexOfCardAddress(address cardAddress) public view returns (uint256) {
require(cardAddressExists(cardAddress));
return _indexOf[cardAddress];
}
function ownerCountOfCard(address owner, address cardAddress) public view returns (uint256) {
// both card addresses need to exist in order to own cards
require(cardAddressExists(owner));
require(cardAddressExists(cardAddress));
// check if it's your own address
if (owner == cardAddress) {
return 0;
}
uint256 ownerCount = 0;
address[] memory owners = _ownersOf[cardAddress];
for (uint256 i = 0; i < owners.length; i++) {
if (owner == owners[i]) {
ownerCount++;
}
}
return ownerCount;
}
function ownerHasCard(address owner, address cardAddress) public view returns (bool doesOwn, uint256[] indexes) {
// both card addresses need to exist in order to own cards
require(cardAddressExists(owner));
require(cardAddressExists(cardAddress));
uint256[] memory ownerIndexes = new uint256[](ownerCountOfCard(owner, cardAddress));
// check if it's your own address
if (owner == cardAddress) {
return (true, ownerIndexes);
}
if (ownerIndexes.length > 0) {
uint256 currentIndex = 0;
address[] memory owners = _ownersOf[cardAddress];
for (uint256 i = 0; i < owners.length; i++) {
if (owner == owners[i]) {
ownerIndexes[currentIndex] = i;
currentIndex++;
}
}
}
// this owner may own multiple copies of the card and so an array of indexes are returned
// if the owner does not own the card, it will return (false, [])
return (ownerIndexes.length > 0, ownerIndexes);
}
function ownerHasCardSimple(address owner, address cardAddress) private view returns (bool) {
bool doesOwn;
(doesOwn, ) = ownerHasCard(owner, cardAddress);
return doesOwn;
}
function ownerCanClaimCard(address owner, address cardAddress, address[3] choices, uint256 amount) private view returns (address currentClaimContender, uint256 claimContenderIndex) {
// you cannot claim back your own address cards
require(owner != cardAddress);
require(cardAddressExists(owner));
require(ownerHasCardSimple(owner, cardAddress) || _cardsOf[owner].length < MAX_UNIQUE_CARDS_PER_ADDRESS);
uint256 cheapestIndex;
bool canClaim;
address claimContender;
uint256 lowestClaimPrice;
(cheapestIndex, canClaim, claimContender, lowestClaimPrice, ) = getCheapestCardWager(cardAddress);
// make sure we can actually claim it and that we are paying the correct amount
require(canClaim);
require(amount == lowestClaimPrice);
// we also need to check that the sender is not the current claim contender
require(owner != claimContender);
// now check if we own all of our choices
for (uint256 i = 0; i < choices.length; i++) {
require(ownerHasCardSimple(owner, choices[i])); // if one is not owned, it will trigger a revert
}
// if no requires have been triggered by this point it means we are able to claim the card
// now return the claim contender and their index
return (claimContender, cheapestIndex);
}
function generateCardsFromClaimForOpponent(address cardAddress, address opponentAddress) private returns (address[3]) {
require(cardAddressExists(cardAddress));
require(cardAddressExists(opponentAddress));
require(ownerHasCardSimple(opponentAddress, cardAddress));
// generate the opponents cards from their own inventory
// it is important to note that at least 1 of their choices
// needs to be the card you are attempting to claim
address[] memory cardsOfOpponent = _cardsOf[opponentAddress];
address[3] memory opponentCardChoices;
uint256 tmpSeed = tmpShuffleSeed(_seed, uint256(opponentAddress));
uint256 tmpModulus;
uint256 indexOfClaimableCard;
(tmpSeed, indexOfClaimableCard) = tmpQuerySeed(tmpSeed, 3); // 0, 1 or 2
for (uint256 i = 0; i < 3; i++) {
if (i == indexOfClaimableCard) {
opponentCardChoices[i] = cardAddress;
} else {
(tmpSeed, tmpModulus) = tmpQuerySeed(tmpSeed, cardsOfOpponent.length);
opponentCardChoices[i] = cardsOfOpponent[tmpModulus];
}
}
// finally we need to update the main seed and as we initially started with
// the current main seed, tmpSeed will be the current representation of the seed
_seed = tmpSeed;
return opponentCardChoices;
}
function updateCardStatistics(address cardAddress) private {
AddressCard storage addressCardClaimed = _addressCards[_indexOf[cardAddress]];
address claimContender;
uint256 lowestClaimPrice;
uint256 wagerCount;
( , , claimContender, lowestClaimPrice, wagerCount) = getCheapestCardWager(cardAddress);
addressCardClaimed._forClaim = uint8(wagerCount);
addressCardClaimed._lowestPrice = lowestClaimPrice;
addressCardClaimed._claimContender = claimContender;
}
function transferCard(address cardAddress, address fromAddress, address toAddress) private {
require(toAddress != fromAddress);
require(cardAddressExists(cardAddress));
require(cardAddressExists(fromAddress));
uint256 firstMatchedIndex;
bool isWagered;
(firstMatchedIndex, isWagered, , , ) = getOwnerOfCardsCheapestWager(fromAddress, cardAddress);
require(isWagered == false); // you cannot transfer a card if it's currently wagered
require(cardAddressExists(toAddress));
require(toAddress != cardAddress); // can't transfer a card to it's original address
require(ownerHasCardSimple(toAddress, cardAddress) || _cardsOf[toAddress].length < MAX_UNIQUE_CARDS_PER_ADDRESS);
// firstly, if toAddress doesn't have a copy we need to add one
if (!ownerHasCardSimple(toAddress, cardAddress)) {
_cardsOf[toAddress].push(cardAddress);
}
// now check whether the fromAddress is just our original card
// address, if this is the case, they are free to transfer out
// one of their cards assuming the claim limit is not yet reached
if (fromAddress == cardAddress) { // the card is being claimed/gifted
AddressCard storage addressCardClaimed = _addressCards[_indexOf[cardAddress]];
require(addressCardClaimed._claimed < CLAIM_LIMIT);
// we need to push new data to our arrays
_ownersOf[cardAddress].push(toAddress);
_ownersClaimPriceOf[cardAddress].push(uint256(0));
// now update the claimed count in the registry
addressCardClaimed._claimed = uint8(_ownersOf[cardAddress].length - 1); // we exclude the original address
} else {
// firstly we need to cache the current index from our fromAddress' _cardsOf
uint256 cardIndexOfSender = getCardIndexOfOwner(cardAddress, fromAddress);
// now just update the address at the firstMatchedIndex
_ownersOf[cardAddress][firstMatchedIndex] = toAddress;
// finally check if our fromAddress has any copies of the card left
if (!ownerHasCardSimple(fromAddress, cardAddress)) {
// if not delete that card from their inventory and make room in the array
for (uint256 i = cardIndexOfSender; i < _cardsOf[fromAddress].length - 1; i++) {
// shuffle the next value over
_cardsOf[fromAddress][i] = _cardsOf[fromAddress][i + 1];
}
// now decrease the length
_cardsOf[fromAddress].length--;
}
}
// now that we're done, it's time to log the event
AddressCardWasTransferred(cardAddress, fromAddress, toAddress);
}
function calculateAdjustedFiguresForBattle(address[3] yourChoices, address[3] opponentsChoices) private view returns (uint256[3][2] allAdjustedAttackFigures, uint256[3][2] allAdjustedDefenceFigures) {
// [0] is yours, [1] is your opponents
AddressCard[3][2] memory allCards;
uint256[3][2] memory allAttackFigures;
uint256[3][2] memory allDefenceFigures;
bool[2] memory allOfSameType = [ true, true ];
uint256[2] memory cumulativeAttackBonuses = [ uint256(0), uint256(0) ];
uint256[2] memory cumulativeDefenceBonuses = [ uint256(0), uint256(0) ];
for (uint256 i = 0; i < 3; i++) {
// cache your cards
require(_exists[yourChoices[i]]);
allCards[0][i] = _addressCards[_indexOf[yourChoices[i]]];
allAttackFigures[0][i] = allCards[0][i]._attack;
allDefenceFigures[0][i] = allCards[0][i]._defence;
// cache your opponents cards
require(_exists[opponentsChoices[i]]);
allCards[1][i] = _addressCards[_indexOf[opponentsChoices[i]]];
allAttackFigures[1][i] = allCards[1][i]._attack;
allDefenceFigures[1][i] = allCards[1][i]._defence;
}
// for the next part, order is quite important as we want the
// addition to happen first and then the multiplication to happen
// at the very end for the type advantages/resistances
//////////////////////////////////////////////////////////////
// the first modifiers that needs to be applied is the
// ALL_ATT, ALL_DEF and the ALL_ATT_DEF mod
// if all 3 of the chosen cards match the same type
// and if at least one of them have the ALL_ATT, ALL_DEF
// or ALL_ATT_DEF modifier, all of the cards will receive
// the cumulative bonus for att/def/att+def
for (i = 0; i < 3; i++) {
// start with your cards
// compare to see if the types are the same as the previous one
if (i > 0 && allCards[0][i]._cardType != allCards[0][i - 1]._cardType) {
allOfSameType[0] = false;
}
// next count up all the modifier values for a total possible bonus
if (allCards[0][i]._cardModifier == uint256(MODIFIER.ALL_ATT)) { // all attack
// for the ALL_ATT modifier, the additional attack bonus is
// stored in the primary value
cumulativeAttackBonuses[0] += allCards[0][i]._modifierPrimarayVal;
} else if (allCards[0][i]._cardModifier == uint256(MODIFIER.ALL_DEF)) { // all defence
// for the ALL_DEF modifier, the additional defence bonus is
// stored in the primary value
cumulativeDefenceBonuses[0] += allCards[0][i]._modifierPrimarayVal;
} else if (allCards[0][i]._cardModifier == uint256(MODIFIER.ALL_ATT_DEF)) { // all attack + defence
// for the ALL_ATT_DEF modifier, the additional attack bonus is
// stored in the primary value and the additional defence bonus is
// stored in the secondary value
cumulativeAttackBonuses[0] += allCards[0][i]._modifierPrimarayVal;
cumulativeDefenceBonuses[0] += allCards[0][i]._modifierSecondaryVal;
}
// now do the same for your opponent
if (i > 0 && allCards[1][i]._cardType != allCards[1][i - 1]._cardType) {
allOfSameType[1] = false;
}
if (allCards[1][i]._cardModifier == uint256(MODIFIER.ALL_ATT)) {
cumulativeAttackBonuses[1] += allCards[1][i]._modifierPrimarayVal;
} else if (allCards[1][i]._cardModifier == uint256(MODIFIER.ALL_DEF)) {
cumulativeDefenceBonuses[1] += allCards[1][i]._modifierPrimarayVal;
} else if (allCards[1][i]._cardModifier == uint256(MODIFIER.ALL_ATT_DEF)) {
cumulativeAttackBonuses[1] += allCards[1][i]._modifierPrimarayVal;
cumulativeDefenceBonuses[1] += allCards[1][i]._modifierSecondaryVal;
}
}
// we void our bonus if they aren't all of the type
if (!allOfSameType[0]) {
cumulativeAttackBonuses[0] = 0;
cumulativeDefenceBonuses[0] = 0;
}
if (!allOfSameType[1]) {
cumulativeAttackBonuses[1] = 0;
cumulativeDefenceBonuses[1] = 0;
}
// now add the bonus figures to the initial attack numbers, they will be 0
// if they either weren't all of the same type or if no cards actually had
// the ALL_ modifier
for (i = 0; i < 3; i++) {
// for your cards
allAttackFigures[0][i] += cumulativeAttackBonuses[0];
allDefenceFigures[0][i] += cumulativeDefenceBonuses[0];
// ...and your opponents cards
allAttackFigures[1][i] += cumulativeAttackBonuses[1];
allDefenceFigures[1][i] += cumulativeDefenceBonuses[1];
}
//////////////////////////////////////////////////////////////
// the second modifier that needs to be applied is the V_ATT
// or the V_DEF mod
// if the versing card matches the same type listed in the
// primaryModifierVal, that card will receive the bonus in
// secondaryModifierVal for att/def
for (i = 0; i < 3; i++) {
// start with your cards
if (allCards[0][i]._cardModifier == uint256(MODIFIER.V_ATT)) { // versing attack
// check if the versing cards type matches the primary value
if (allCards[1][i]._cardType == allCards[0][i]._modifierPrimarayVal) {
// add the attack bonus (amount is held in the secondary value)
allAttackFigures[0][i] += allCards[0][i]._modifierSecondaryVal;
}
} else if (allCards[0][i]._cardModifier == uint256(MODIFIER.V_DEF)) { // versing defence
// check if the versing cards type matches the primary value
if (allCards[1][i]._cardType == allCards[0][i]._modifierPrimarayVal) {
// add the defence bonus (amount is held in the secondary value)
allDefenceFigures[0][i] += allCards[0][i]._modifierSecondaryVal;
}
}
// now do the same for your opponent
if (allCards[1][i]._cardModifier == uint256(MODIFIER.V_ATT)) {
if (allCards[0][i]._cardType == allCards[1][i]._modifierPrimarayVal) {
allAttackFigures[1][i] += allCards[1][i]._modifierSecondaryVal;
}
} else if (allCards[1][i]._cardModifier == uint256(MODIFIER.V_DEF)) {
if (allCards[0][i]._cardType == allCards[1][i]._modifierPrimarayVal) {
allDefenceFigures[1][i] += allCards[1][i]._modifierSecondaryVal;
}
}
}
//////////////////////////////////////////////////////////////
// the third modifier that needs to be applied is the type
// advantage numbers as well as applying R_V (resists versing
// cards type advantage) and A_I (increases your cards advantage)
for (i = 0; i < 3; i++) {
// start with your cards
// first check if the card we're versing resists our type advantage
if (allCards[1][i]._cardModifier != uint256(MODIFIER.R_V)) {
// test all the possible combinations of advantages
if (
// fire vs nature
(allCards[0][i]._cardType == uint256(TYPE.FIRE) && allCards[1][i]._cardType == uint256(TYPE.NATURE)) ||
// water vs fire
(allCards[0][i]._cardType == uint256(TYPE.WATER) && allCards[1][i]._cardType == uint256(TYPE.FIRE)) ||
// nature vs water
(allCards[0][i]._cardType == uint256(TYPE.NATURE) && allCards[1][i]._cardType == uint256(TYPE.WATER))
) {
// now check if your card has a type advantage increase modifier
if (allCards[0][i]._cardModifier == uint256(MODIFIER.A_I)) {
allAttackFigures[0][i] = SafeMath.div(SafeMath.mul(allAttackFigures[0][i], 3), 2); // x1.5
allDefenceFigures[0][i] = SafeMath.div(SafeMath.mul(allDefenceFigures[0][i], 3), 2); // x1.5
} else {
allAttackFigures[0][i] = SafeMath.div(SafeMath.mul(allAttackFigures[0][i], 5), 4); // x1.25
allDefenceFigures[0][i] = SafeMath.div(SafeMath.mul(allDefenceFigures[0][i], 5), 4); // x1.25
}
}
}
// now do the same for your opponent
if (allCards[0][i]._cardModifier != uint256(MODIFIER.R_V)) {
if (
(allCards[1][i]._cardType == uint256(TYPE.FIRE) && allCards[0][i]._cardType == uint256(TYPE.NATURE)) ||
(allCards[1][i]._cardType == uint256(TYPE.WATER) && allCards[0][i]._cardType == uint256(TYPE.FIRE)) ||
(allCards[1][i]._cardType == uint256(TYPE.NATURE) && allCards[0][i]._cardType == uint256(TYPE.WATER))
) {
if (allCards[1][i]._cardModifier == uint256(MODIFIER.A_I)) {
allAttackFigures[1][i] = SafeMath.div(SafeMath.mul(allAttackFigures[1][i], 3), 2); // x1.5
allDefenceFigures[1][i] = SafeMath.div(SafeMath.mul(allDefenceFigures[1][i], 3), 2); // x1.5
} else {
allAttackFigures[1][i] = SafeMath.div(SafeMath.mul(allAttackFigures[1][i], 5), 4); // x1.25
allDefenceFigures[1][i] = SafeMath.div(SafeMath.mul(allDefenceFigures[1][i], 5), 4); // x1.25
}
}
}
}
//////////////////////////////////////////////////////////////
// the final modifier that needs to be applied is the V_SWAP mod
// if your card has this modifier, it will swap the final attack
// and defence numbers of your card
uint256 tmp;
for (i = 0; i < 3; i++) {
// start with your cards
// check if the versing card has the V_SWAP modifier
if (allCards[1][i]._cardModifier == uint256(MODIFIER.V_SWAP)) {
tmp = allAttackFigures[0][i];
allAttackFigures[0][i] = allDefenceFigures[0][i];
allDefenceFigures[0][i] = tmp;
}
// ...and your opponents cards
if (allCards[0][i]._cardModifier == uint256(MODIFIER.V_SWAP)) {
tmp = allAttackFigures[1][i];
allAttackFigures[1][i] = allDefenceFigures[1][i];
allDefenceFigures[1][i] = tmp;
}
}
// we're all done!
return (allAttackFigures, allDefenceFigures);
}
//////////////////////////////////////////////////////////////////////
// Getter Functions
function getCard(address cardAddress) public view returns (uint256 cardIndex, uint256 cardType, uint256 cardModifier, uint256 cardModifierPrimaryVal, uint256 cardModifierSecondaryVal, uint256 attack, uint256 defence, uint256 claimed, uint256 forClaim, uint256 lowestPrice, address claimContender) {
require(cardAddressExists(cardAddress));
uint256 index = _indexOf[cardAddress];
AddressCard memory addressCard = _addressCards[index];
return (
index,
uint256(addressCard._cardType),
uint256(addressCard._cardModifier),
uint256(addressCard._modifierPrimarayVal),
uint256(addressCard._modifierSecondaryVal),
uint256(addressCard._attack),
uint256(addressCard._defence),
uint256(addressCard._claimed),
uint256(addressCard._forClaim),
uint256(addressCard._lowestPrice),
address(addressCard._claimContender)
);
}
function getCheapestCardWager(address cardAddress) public view returns (uint256 cheapestIndex, bool isClaimable, address claimContender, uint256 claimPrice, uint256 wagerCount) {
require(cardAddressExists(cardAddress));
uint256 cheapestSale = 0;
uint256 indexOfCheapestSale = 0;
uint256 totalWagers = 0;
uint256[] memory allOwnersClaimPrice = _ownersClaimPriceOf[cardAddress];
for (uint256 i = 0; i < allOwnersClaimPrice.length; i++) {
uint256 priceAtIndex = allOwnersClaimPrice[i];
if (priceAtIndex != 0) {
totalWagers++;
if (cheapestSale == 0 || priceAtIndex < cheapestSale) {
cheapestSale = priceAtIndex;
indexOfCheapestSale = i;
}
}
}
return (
indexOfCheapestSale,
(cheapestSale > 0),
(cheapestSale > 0 ? _ownersOf[cardAddress][indexOfCheapestSale] : address(0)),
cheapestSale,
totalWagers
);
}
function getOwnerOfCardsCheapestWager(address owner, address cardAddress) public view returns (uint256 cheapestIndex, bool isSelling, uint256 claimPrice, uint256 priceRank, uint256 outOf) {
bool doesOwn;
uint256[] memory indexes;
(doesOwn, indexes) = ownerHasCard(owner, cardAddress);
require(doesOwn);
uint256[] memory allOwnersClaimPrice = _ownersClaimPriceOf[cardAddress];
uint256 cheapestSale = 0;
uint256 indexOfCheapestSale = 0; // this will handle the case of owner == cardAddress
if (indexes.length > 0) {
indexOfCheapestSale = indexes[0]; // defaults to the first index matched
} else { // also will handle the case of owner == cardAddress
cheapestSale = allOwnersClaimPrice[0];
}
for (uint256 i = 0; i < indexes.length; i++) {
if (allOwnersClaimPrice[indexes[i]] != 0 && (cheapestSale == 0 || allOwnersClaimPrice[indexes[i]] < cheapestSale)) {
cheapestSale = allOwnersClaimPrice[indexes[i]];
indexOfCheapestSale = indexes[i];
}
}
uint256 saleRank = 0;
uint256 totalWagers = 0;
if (cheapestSale > 0) {
saleRank = 1;
for (i = 0; i < allOwnersClaimPrice.length; i++) {
if (allOwnersClaimPrice[i] != 0) {
totalWagers++;
if (allOwnersClaimPrice[i] < cheapestSale) {
saleRank++;
}
}
}
}
return (
indexOfCheapestSale,
(cheapestSale > 0),
cheapestSale,
saleRank,
totalWagers
);
}
function getCardIndexOfOwner(address cardAddress, address owner) public view returns (uint256) {
require(cardAddressExists(cardAddress));
require(cardAddressExists(owner));
require(ownerHasCardSimple(owner, cardAddress));
uint256 matchedIndex;
address[] memory cardsOfOwner = _cardsOf[owner];
for (uint256 i = 0; i < cardsOfOwner.length; i++) {
if (cardsOfOwner[i] == cardAddress) {
matchedIndex = i;
break;
}
}
return matchedIndex;
}
function getTotalUniqueCards() public view returns (uint256) {
return _addressCards.length;
}
function getAllCardsAddress() public view returns (bytes20[]) {
bytes20[] memory allCardsAddress = new bytes20[](_addressCards.length);
for (uint256 i = 0; i < _addressCards.length; i++) {
AddressCard memory addressCard = _addressCards[i];
allCardsAddress[i] = bytes20(addressCard._cardAddress);
}
return allCardsAddress;
}
function getAllCardsType() public view returns (bytes1[]) {
bytes1[] memory allCardsType = new bytes1[](_addressCards.length);
for (uint256 i = 0; i < _addressCards.length; i++) {
AddressCard memory addressCard = _addressCards[i];
allCardsType[i] = bytes1(addressCard._cardType);
}
return allCardsType;
}
function getAllCardsModifier() public view returns (bytes1[]) {
bytes1[] memory allCardsModifier = new bytes1[](_addressCards.length);
for (uint256 i = 0; i < _addressCards.length; i++) {
AddressCard memory addressCard = _addressCards[i];
allCardsModifier[i] = bytes1(addressCard._cardModifier);
}
return allCardsModifier;
}
function getAllCardsModifierPrimaryVal() public view returns (bytes1[]) {
bytes1[] memory allCardsModifierPrimaryVal = new bytes1[](_addressCards.length);
for (uint256 i = 0; i < _addressCards.length; i++) {
AddressCard memory addressCard = _addressCards[i];
allCardsModifierPrimaryVal[i] = bytes1(addressCard._modifierPrimarayVal);
}
return allCardsModifierPrimaryVal;
}
function getAllCardsModifierSecondaryVal() public view returns (bytes1[]) {
bytes1[] memory allCardsModifierSecondaryVal = new bytes1[](_addressCards.length);
for (uint256 i = 0; i < _addressCards.length; i++) {
AddressCard memory addressCard = _addressCards[i];
allCardsModifierSecondaryVal[i] = bytes1(addressCard._modifierSecondaryVal);
}
return allCardsModifierSecondaryVal;
}
function getAllCardsAttack() public view returns (bytes1[]) {
bytes1[] memory allCardsAttack = new bytes1[](_addressCards.length);
for (uint256 i = 0; i < _addressCards.length; i++) {
AddressCard memory addressCard = _addressCards[i];
allCardsAttack[i] = bytes1(addressCard._attack);
}
return allCardsAttack;
}
function getAllCardsDefence() public view returns (bytes1[]) {
bytes1[] memory allCardsDefence = new bytes1[](_addressCards.length);
for (uint256 i = 0; i < _addressCards.length; i++) {
AddressCard memory addressCard = _addressCards[i];
allCardsDefence[i] = bytes1(addressCard._defence);
}
return allCardsDefence;
}
function getAllCardsClaimed() public view returns (bytes1[]) {
bytes1[] memory allCardsClaimed = new bytes1[](_addressCards.length);
for (uint256 i = 0; i < _addressCards.length; i++) {
AddressCard memory addressCard = _addressCards[i];
allCardsClaimed[i] = bytes1(addressCard._claimed);
}
return allCardsClaimed;
}
function getAllCardsForClaim() public view returns (bytes1[]) {
bytes1[] memory allCardsForClaim = new bytes1[](_addressCards.length);
for (uint256 i = 0; i < _addressCards.length; i++) {
AddressCard memory addressCard = _addressCards[i];
allCardsForClaim[i] = bytes1(addressCard._forClaim);
}
return allCardsForClaim;
}
function getAllCardsLowestPrice() public view returns (bytes32[]) {
bytes32[] memory allCardsLowestPrice = new bytes32[](_addressCards.length);
for (uint256 i = 0; i < _addressCards.length; i++) {
AddressCard memory addressCard = _addressCards[i];
allCardsLowestPrice[i] = bytes32(addressCard._lowestPrice);
}
return allCardsLowestPrice;
}
function getAllCardsClaimContender() public view returns (bytes4[]) {
// returns the indexes of the claim contender
bytes4[] memory allCardsClaimContender = new bytes4[](_addressCards.length);
for (uint256 i = 0; i < _addressCards.length; i++) {
AddressCard memory addressCard = _addressCards[i];
allCardsClaimContender[i] = bytes4(_indexOf[addressCard._claimContender]);
}
return allCardsClaimContender;
}
function getAllOwnersOfCard(address cardAddress) public view returns (bytes4[]) {
require(cardAddressExists(cardAddress));
// returns the indexes of the owners
address[] memory ownersOfCardAddress = _ownersOf[cardAddress];
bytes4[] memory allOwners = new bytes4[](ownersOfCardAddress.length);
for (uint256 i = 0; i < ownersOfCardAddress.length; i++) {
allOwners[i] = bytes4(_indexOf[ownersOfCardAddress[i]]);
}
return allOwners;
}
function getAllOwnersClaimPriceOfCard(address cardAddress) public view returns (bytes32[]) {
require(cardAddressExists(cardAddress));
uint256[] memory ownersClaimPriceOfCardAddress = _ownersClaimPriceOf[cardAddress];
bytes32[] memory allOwnersClaimPrice = new bytes32[](ownersClaimPriceOfCardAddress.length);
for (uint256 i = 0; i < ownersClaimPriceOfCardAddress.length; i++) {
allOwnersClaimPrice[i] = bytes32(ownersClaimPriceOfCardAddress[i]);
}
return allOwnersClaimPrice;
}
function getAllCardAddressesOfOwner(address owner) public view returns (bytes4[]) {
require(cardAddressExists(owner));
// returns the indexes of the cards owned
address[] memory cardsOfOwner = _cardsOf[owner];
bytes4[] memory allCardAddresses = new bytes4[](cardsOfOwner.length);
for (uint256 i = 0; i < cardsOfOwner.length; i++) {
allCardAddresses[i] = bytes4(_indexOf[cardsOfOwner[i]]);
}
return allCardAddresses;
}
function getAllCardAddressesCountOfOwner(address owner) public view returns (bytes1[]) {
require(cardAddressExists(owner));
address[] memory cardsOfOwner = _cardsOf[owner];
bytes1[] memory allCardAddressesCount = new bytes1[](cardsOfOwner.length);
for (uint256 i = 0; i < cardsOfOwner.length; i++) {
allCardAddressesCount[i] = bytes1(ownerCountOfCard(owner, cardsOfOwner[i]));
}
return allCardAddressesCount;
}
function getAllCardAddressesPriceOfOwner(address owner) public view returns (bytes32[]) {
require(cardAddressExists(owner));
address[] memory cardsOfOwner = _cardsOf[owner];
bytes32[] memory allCardAddressesPrice = new bytes32[](cardsOfOwner.length);
for (uint256 i = 0; i < cardsOfOwner.length; i++) {
uint256 price;
( , , price, , ) = getOwnerOfCardsCheapestWager(owner, cardsOfOwner[i]);
allCardAddressesPrice[i] = bytes32(price);
}
return allCardAddressesPrice;
}
//////////////////////////////////////////////////////////////////////
}
library SafeMath {
/**
* @dev Multiplies two numbers, throws on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
assert(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Substracts two numbers, throws on overflow (ie if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
{
"compilationTarget": {
"AddressWarsBeta.sol": "AddressWarsBeta"
},
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"cardAddress","type":"address"}],"name":"ownerHasCard","outputs":[{"name":"doesOwn","type":"bool"},{"name":"indexes","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"tmpSeed","type":"uint256"},{"name":"mix","type":"uint256"}],"name":"tmpShuffleSeed","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"withdrawAmount","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"cardAddress","type":"address"}],"name":"cancelWagerOfCard","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"cardAddress","type":"address"},{"name":"amount","type":"uint256"}],"name":"wagerCardForAmount","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"getAllCardsType","outputs":[{"name":"","type":"bytes1[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getAllCardsLowestPrice","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"getAllCardAddressesOfOwner","outputs":[{"name":"","type":"bytes4[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getAllCardsForClaim","outputs":[{"name":"","type":"bytes1[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"enlist","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"cardAddress","type":"address"}],"name":"getCheapestCardWager","outputs":[{"name":"cheapestIndex","type":"uint256"},{"name":"isClaimable","type":"bool"},{"name":"claimContender","type":"address"},{"name":"claimPrice","type":"uint256"},{"name":"wagerCount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalUniqueCards","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"tmpSeed","type":"uint256"},{"name":"modulus","type":"uint256"}],"name":"tmpQuerySeed","outputs":[{"name":"tmpShuffledSeed","type":"uint256"},{"name":"result","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getAllCardsAttack","outputs":[{"name":"","type":"bytes1[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"cardAddress","type":"address"}],"name":"getAllOwnersOfCard","outputs":[{"name":"","type":"bytes4[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getAllCardsModifier","outputs":[{"name":"","type":"bytes1[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"getAllCardAddressesPriceOfOwner","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"getAllCardAddressesCountOfOwner","outputs":[{"name":"","type":"bytes1[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"cardAddress","type":"address"},{"name":"owner","type":"address"}],"name":"getCardIndexOfOwner","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getAllCardsModifierPrimaryVal","outputs":[{"name":"","type":"bytes1[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"withdrawAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getAllCardsDefence","outputs":[{"name":"","type":"bytes1[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"dev","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"cardAddress","type":"address"}],"name":"getOwnerOfCardsCheapestWager","outputs":[{"name":"cheapestIndex","type":"uint256"},{"name":"isSelling","type":"bool"},{"name":"claimPrice","type":"uint256"},{"name":"priceRank","type":"uint256"},{"name":"outOf","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getAllCardsClaimed","outputs":[{"name":"","type":"bytes1[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"cardAddress","type":"address"}],"name":"getAllOwnersClaimPriceOfCard","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"cardAddress","type":"address"},{"name":"choices","type":"address[3]"}],"name":"attemptToClaimCard","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"getAllCardsModifierSecondaryVal","outputs":[{"name":"","type":"bytes1[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"cardAddress","type":"address"}],"name":"cardAddressExists","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"cardAddress","type":"address"},{"name":"toAddress","type":"address"}],"name":"transferCardTo","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBalanceOfSender","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"cardAddress","type":"address"}],"name":"ownerCountOfCard","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getAllCardsClaimContender","outputs":[{"name":"","type":"bytes4[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"cardAddress","type":"address"}],"name":"getCard","outputs":[{"name":"cardIndex","type":"uint256"},{"name":"cardType","type":"uint256"},{"name":"cardModifier","type":"uint256"},{"name":"cardModifierPrimaryVal","type":"uint256"},{"name":"cardModifierSecondaryVal","type":"uint256"},{"name":"attack","type":"uint256"},{"name":"defence","type":"uint256"},{"name":"claimed","type":"uint256"},{"name":"forClaim","type":"uint256"},{"name":"lowestPrice","type":"uint256"},{"name":"claimContender","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getAllCardsAddress","outputs":[{"name":"","type":"bytes20[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"cardAddress","type":"address"}],"name":"indexOfCardAddress","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"enlistedAddress","type":"address"}],"name":"AddressDidEnlist","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"addressCard","type":"address"},{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"wagerAmount","type":"uint256"}],"name":"AddressCardWasWagered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"addressCard","type":"address"},{"indexed":false,"name":"owner","type":"address"}],"name":"AddressCardWagerWasCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"addressCard","type":"address"},{"indexed":false,"name":"fromAddress","type":"address"},{"indexed":false,"name":"toAddress","type":"address"}],"name":"AddressCardWasTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"wasSuccessful","type":"bool"},{"indexed":false,"name":"addressCard","type":"address"},{"indexed":false,"name":"claimer","type":"address"},{"indexed":false,"name":"claimContender","type":"address"},{"indexed":false,"name":"claimerChoices","type":"address[3]"},{"indexed":false,"name":"claimContenderChoices","type":"address[3]"},{"indexed":false,"name":"allFinalAttackValues","type":"uint256[3][2]"},{"indexed":false,"name":"allFinalDefenceValues","type":"uint256[3][2]"}],"name":"ClaimAttempt","type":"event"}]