🏭 zombiefactory.sol
- 🍞 zombiefeeding.sol
- 👼 zombieHelper.sol
- 💥 zombieAttack.sol
uint levelUpFee = 0.001 ether;
...
function levelUp(uint _zombieId) external payable {
require(msg.value == levelUpFee);
zombies[_zombieId].level++;
}
payable함수의 트랜잭션을 봉투라고 생각하고 파라미터를 편지내용이라고 생각하면 좋다
트랜잭션
: 봉투함수의 파라미터
: 편지 내용, value : 돈
function withdraw() external onlyOwner {
owner.transfer(this.balance);
}
function setLevelUpFee(uint _fee) external onlyOwner {
levelUpFee = _fee;
}
transfer
함수를 사용해서 이더를 특정 주소로 전달할 수 있다.
그리고 this.balance
는 컨트랙트에 저장돼있는 전체 잔액을 말한다.
pragma solidity ^0.4.19;
import "./zombiehelper.sol";
contract ZombieBattle is ZombieHelper {
}
이 방법이 완전하고 안전한 방법의 난수 생성은 아니다 - 해결방법
import "./zombiehelper.sol";
contract ZombieBattle is ZombieHelper {
uint randNonce = 0;
function randMod(uint _modulus) internal returns(uint) {
randNonce++;
//여기가 난수 생성부분
return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
}
}
keccak256 해시함수
의 파라미터로now
의 타임스탬프 값,msg.sender
, 증가하는randNonce
(딱 한 번만 사용되는 숫자, 즉 똑같은 입력으로 두 번 이상 동일한 해시 함수를 실행할 수 없게 함)를 받고 있다.그리고서
keccak
을 사용하여 이 입력들을 임의의 해시 값으로 변환하고,
변환한 해시 값을uint
로 바꾼 후,% 100
을 써서 마지막 2자리 숫자만 받도록 했다.이를 통해 0과 99 사이의 완전한 난수를 얻을 수 있다.
import "./zombiehelper.sol";
contract ZombieBattle is ZombieHelper {
...
function attack(uint _zombieId, uint _targetId) external {
}
}
좀비 전투는 다음과 같이 진행될 것이다
나의 좀비 중 하나를 고르고(_zombieId), 상대방의 좀비를 공격 대상으로 선택한다(_targetId).
내가 공격하는 쪽의 좀비라면, 나는 70%의 승리 확률을 가진다. 방어하는 쪽의 좀비는 30%의 승리 확률을 가질 것이다.
모든 좀비들(공격, 방어 모두)은 전투 결과에 따라 증가하는 winCount와 lossCount를 가질 것이다.
공격하는 쪽의 좀비가 이기면, 좀비의 레벨이 오르고 새로운 좀비가 생긴다.
좀비가 지면, 아무것도 일어나지 않는다(좀비의 lossCount가 증가하는 것 빼고).
좀비가 이기든 지든, 공격하는 쪽 좀비의 재사용 대기시간이 활성화될 것이다.
...
contract ZombieFeeding is ZombieFactory {
KittyInterface kittyContract;
// 함수를 실행하는자가 _zombieId의 소유자인지 확인하고 싶은 과정을 여러번 쓰고 싶음으로 해당 require를 modifier로 만들어서 여러번 쓸 수 있도록 해준다
modifier ownerOf(uint _zombieId) {
require(msg.sender == zombieToOwner[_zombieId]);
_;
}
...
// 함수 제어자에 ownerOf라는 modifier를 추가해줬다
function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal ownerOf(_zombieId) {
Zombie storage myZombie = zombies[_zombieId];
require(_isReady(myZombie));
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
if (keccak256(_species) == keccak256("kitty")) {
newDna = newDna - newDna % 100 + 99;
}
_createZombie("NoName", newDna);
_triggerCooldown(myZombie);
}
...
함수를 실행하는자가
_zombieId
의 소유자인지 확인하고 싶은 과정을 여러번 쓰고 싶음으로 해당require
를modifier
로 만들어서 여러번 쓸 수 있도록 해준다
...
contract ZombieHelper is ZombieFeeding {
...
function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) ownerOf(_zombieId) {
zombies[_zombieId].name = _newName;
}
function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) ownerOf(_zombieId) {
zombies[_zombieId].dna = _newDna;
}
...
import "./zombiehelper.sol";
contract ZombieBattle is ZombieHelper {
uint randNonce = 0;
uint attackVictoryProbability = 70;
function randMod(uint _modulus) internal returns(uint) {
randNonce++;
return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
}
function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
Zombie storage myZombie = zombies[_zombieId];
Zombie storage enemyZombie = zombies[_targetId];
uint rand = randMod(100);
}
}
attack함수
에ownerOf modifier
추가
(attack을 하고자하는 zombie가 내가 소유한 zombie임을 확인해야 하기 때문임)
pragma solidity ^0.4.19;
import "./ownable.sol";
contract ZombieFactory is Ownable {
...
struct Zombie {
string name;
uint dna;
uint32 level;
uint32 readyTime;
// attack함수를 구현하기 위해 Zombie구조체에 winCount, lossCount변수를 추가함
uint16 winCount;
uint16 lossCount;
}
...
function _createZombie(string _name, uint _dna) internal {
// 위의 Zombie struct의 구조가 변경되었기 때문에 zombie생성시 입력되는 값도 추가로 수정해줌(처음 생성된 zombie는 0승 0패를 가지고 있음)
uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
zombieToOwner[id] = msg.sender;
ownerZombieCount[msg.sender]++;
NewZombie(id, _name, _dna);
}
...
}
위의
Zombie struct
의 구조가 변경되었기 때문에
zombie생성시 입력되는 값도 추가로 수정해줌(처음 생성된 zombie는 0승 0패를 가지고 있음)
import "./zombiehelper.sol";
contract ZombieBattle is ZombieHelper {
...
function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
Zombie storage myZombie = zombies[_zombieId];
Zombie storage enemyZombie = zombies[_targetId];
uint rand = randMod(100);
//attack이 성공한다면(난수 rand의 값이 70보다 작다면, 70%확률로 승리)
//myZombie의 구조체 숫자를 알맞게 변경시켜주고 enemyZombie를 먹이로 준다
if (rand <= attackVictoryProbability) {
myZombie.winCount++;
myZombie.level++;
enemyZombie.lossCount++;
feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
//attack이 실패한다면(난수 rand의 값이 70보다 크다면, 30%확률로 패배)
//myZombie의 구조체 숫자를 알맞게 변경시켜준다
} else {
myZombie.lossCount++;
enemyZombie.winCount++;
}
//attack에 대한 행동이 끝났으니 cooldowntime을 부여한다
_triggerCooldown(myZombie);
}
}
attack이 성공
한다면(난수 rand의 값이 70보다 작다면, 70%확률로 승리)
myZombie
의 구조체 숫자를 알맞게 변경시켜주고 enemyZombie를 먹이로 준다
attack이 실패
한다면(난수 rand의 값이 70보다 크다면, 30%확률로 패배)
myZombie
의 구조체 숫자를 알맞게 변경시켜준다attack에 대한 행동이 끝났으니
cooldowntime
을 부여한다
pragma solidity ^0.4.19;
import "./zombiefeeding.sol";
contract ZombieHelper is ZombieFeeding {
uint levelUpFee = 0.001 ether;
modifier aboveLevel(uint _level, uint _zombieId) {
require(zombies[_zombieId].level >= _level);
_;
}
function withdraw() external onlyOwner {
owner.transfer(this.balance);
}
function setLevelUpFee(uint _fee) external onlyOwner {
levelUpFee = _fee;
}
function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) ownerOf(_zombieId) {
zombies[_zombieId].name = _newName;
}
function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) ownerOf(_zombieId) {
zombies[_zombieId].dna = _newDna;
}
function getZombiesByOwner(address _owner) external view returns(uint[]) {
uint[] memory result = new uint[](ownerZombieCount[_owner]);
uint counter = 0;
for (uint i = 0; i < zombies.length; i++) {
if (zombieToOwner[i] == _owner) {
result[counter] = i;
counter++;
}
}
return result;
}
}
pragma solidity ^0.4.19;
import "./zombiefactory.sol";
contract KittyInterface {
function getKitty(uint256 _id) external view returns (
bool isGestating,
bool isReady,
uint256 cooldownIndex,
uint256 nextActionAt,
uint256 siringWithId,
uint256 birthTime,
uint256 matronId,
uint256 sireId,
uint256 generation,
uint256 genes
);
}
contract ZombieFeeding is ZombieFactory {
KittyInterface kittyContract;
modifier ownerOf(uint _zombieId) {
require(msg.sender == zombieToOwner[_zombieId]);
_;
}
function setKittyContractAddress(address _address) external onlyOwner {
kittyContract = KittyInterface(_address);
}
function _triggerCooldown(Zombie storage _zombie) internal {
_zombie.readyTime = uint32(now + cooldownTime);
}
function _isReady(Zombie storage _zombie) internal view returns (bool) {
return (_zombie.readyTime <= now);
}
function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal ownerOf(_zombieId) {
Zombie storage myZombie = zombies[_zombieId];
require(_isReady(myZombie));
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
if (keccak256(_species) == keccak256("kitty")) {
newDna = newDna - newDna % 100 + 99;
}
_createZombie("NoName", newDna);
_triggerCooldown(myZombie);
}
function feedOnKitty(uint _zombieId, uint _kittyId) public {
uint kittyDna;
(,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
feedAndMultiply(_zombieId, kittyDna, "kitty");
}
}
pragma solidity ^0.4.19;
import "./ownable.sol";
contract ZombieFactory is Ownable {
event NewZombie(uint zombieId, string name, uint dna);
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
uint cooldownTime = 1 days;
struct Zombie {
string name;
uint dna;
uint32 level;
uint32 readyTime;
uint16 winCount;
uint16 lossCount;
}
Zombie[] public zombies;
mapping (uint => address) public zombieToOwner;
mapping (address => uint) ownerZombieCount;
function _createZombie(string _name, uint _dna) internal {
uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
zombieToOwner[id] = msg.sender;
ownerZombieCount[msg.sender]++;
NewZombie(id, _name, _dna);
}
function _generateRandomDna(string _str) private view returns (uint) {
uint rand = uint(keccak256(_str));
return rand % dnaModulus;
}
function createRandomZombie(string _name) public {
require(ownerZombieCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
randDna = randDna - randDna % 100;
_createZombie(_name, randDna);
}
}
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() public {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
import "./zombiehelper.sol";
contract ZombieBattle is ZombieHelper {
uint randNonce = 0;
uint attackVictoryProbability = 70;
function randMod(uint _modulus) internal returns(uint) {
randNonce++;
return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
}
function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
Zombie storage myZombie = zombies[_zombieId];
Zombie storage enemyZombie = zombies[_targetId];
uint rand = randMod(100);
if (rand <= attackVictoryProbability) {
myZombie.winCount++;
myZombie.level++;
enemyZombie.lossCount++;
feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
} else {
myZombie.lossCount++;
enemyZombie.winCount++;
}
_triggerCooldown(myZombie);
}
}