diff --git a/en/index.json b/en/index.json index ad47b5180..f38874bf0 100644 --- a/en/index.json +++ b/en/index.json @@ -91,9 +91,9 @@ "Share this URL so your friends can check out your army:" ], "footer": [ - "Lesson 4 coming in 1-2 weeks", - "You'll get an email from us as soon as it's ready.", - "In the meantime, join us on {telegramLink}, or follow our {twitterLink} to join the conversation!" + "Enjoying CryptoZombies? Let us know!", + "Join the conversation on {telegramLink}, or follow us on {twitterLink}", + "When you're ready, click the button below to access Lesson 4:" ], "zombieDesc": "A Level 3 CryptoZombie", "shareLinkText": "I just completed #CryptoZombies Lesson 3! Check out my zombie army:" @@ -113,7 +113,7 @@ "In the meantime, join us on {telegramLink}, or follow our {twitterLink} to join the conversation!" ], "zombieDesc": "A Level {levelNum} CryptoZombie", - "shareLinkText": "I just completed #CryptoZombies Lesson 4! Check out my zombie army:" + "shareLinkText": "I just completed #CryptoZombies Lesson 4, and defeated the evil IOTA zombie! Try it out yourself:" } }, "zombieBattle": { @@ -218,7 +218,7 @@ }, "lesson4": { "achievementPreamble": [ - "Your friend has completed CryptoZombies Lesson 4, and defeated the evil Iota zombie!", + "Your friend has completed CryptoZombies Lesson 4, and defeated the evil IOTA zombie!", "Go ahead and try it for yourself to the left. Use your friend's army to engage in battle!", "In order to complete Lesson 4 of CryptoZombies, your friend learned about:" ], diff --git a/en/share-template-msgs.json b/en/share-template-msgs.json index f04c9c3f9..aa3b9f382 100644 --- a/en/share-template-msgs.json +++ b/en/share-template-msgs.json @@ -12,7 +12,7 @@ "ogDesc": "I just completed CryptoZombies Lesson 3! My zombie army is growing..." }, "4": { - "pageTitle": "Check out my IOTA0killer!", - "ogDesc": "I just completed CryptoZombies Lesson 4! My zombie won its first battle!" + "pageTitle": "My CryptoZombie defeated the evil IOTA zombie!", + "ogDesc": "I just completed CryptoZombies Lesson 4, and defeated the evil IOTA zombie! Come relive the battle..." } } diff --git a/fr/index.json b/fr/index.json index 4374b1994..516e43921 100644 --- a/fr/index.json +++ b/fr/index.json @@ -91,9 +91,9 @@ "Partagez cette URL afin que vos amis puissent voir votre armée :" ], "footer": [ - "La Leçon 4 arrive dans 1 à 2 semaines", - "On vous préviendra par mail quand elle sera prête.", - "En attendant, rejoignez nous sur {telegramLink}, ou suivez nous sur {twitterLink} pour participer à la conversation !" + "Vous aimez CryptoZombies? Laissez-nous savoir !", + "Rejoignez la conversation sur {telegramLink}, ou suivez-nous sur {twitterLink}", + "Quand vous êtes prêt, cliquez sur le bouton pour accéder à la Leçon 4 :" ], "zombieDesc": "Un CryptoZombie de Niveau 3", "shareLinkText": "Je viens juste de finir la Leçon 3 de #CryptoZombies ! Viens voir mon armée de zombie :" diff --git a/jp/index.json b/jp/index.json index 372013718..4aa7eefb5 100644 --- a/jp/index.json +++ b/jp/index.json @@ -91,9 +91,9 @@ "このURLを送れば友達はいつでもゾンビ軍団を見れるぞ:" ], "footer": [ - "レッスン 4は、1-2週間後にリリース予定です", - "リリースしたらすぐにメールをお送りします。", - "{telegramLink}に参加するか, {twitterLink}をフォローして最新情報をチェックできます!" + "CryptoZombiesはいかがでしたか?ご感想をこちらで受け付けています", + "{telegramLink}に参加するか, {twitterLink}をフォロー", + "準備ができたら、下のボタンを押すとレッスン4に進みます:" ], "zombieDesc": "クリプトゾンビ レベル 3", "shareLinkText": "#CryptZombiesのレッスン3が終わったよ!作ったゾンビ軍団はこんな感じ:" diff --git a/ko/2/00-overview.md b/ko/2/00-overview.md new file mode 100644 index 000000000..c5449015e --- /dev/null +++ b/ko/2/00-overview.md @@ -0,0 +1,11 @@ +--- +title: 좀비가 희생물을 공격하다 +header: 그러니까 자네가 레슨 2에 왔다는 거군! +roadmap: roadmap2.jpg +--- + +훌륭하다, 인간이여! 내가 생각했던 것보다 자네가 코딩을 더 잘하는군. + +레슨 2는 **좀비에게 다른 생명체를 먹여서 좀비 군대의 규모를 늘리는 방법을** 자네에게 가르쳐 줄 걸세. + +이 레슨에서 몇 가지 고급 솔리디티 개념에 대해 다룰 것이니, 시작하기 전에 레슨 1을 완료하는 걸 강력 추천하네. diff --git a/ko/2/1-overview.md b/ko/2/1-overview.md new file mode 100644 index 000000000..6d4948075 --- /dev/null +++ b/ko/2/1-overview.md @@ -0,0 +1,34 @@ +--- +title: 레슨 2 개요 +actions: ['정답 확인하기', '힌트 보기'] +material: + saveZombie: false + zombieBattle: + zombie: + lesson: 1 + humanBattle: true + ignoreZombieCache: true + answer: 1 +--- + +지난 레슨에서 좀비의 이름을 받아서 랜덤으로 좀비를 생성한 다음, 이 좀비를 블록체인 상의 우리 앱 좀비 데이터베이스에 추가하는 함수를 생성했지. + +이번 레슨에서는 우리 앱을 좀 더 게임답게 만들 걸세: 앱을 멀티플레이어 게임으로 만들고, 좀비를 랜덤으로만 생성하지 않고 좀더 재미있는 방식으로 좀비를 생성할 것이네. + +어떻게 새로운 좀비를 생성할까? 좀비가 다른 생명체를 "먹도록" 해서! + +## 좀비 먹이기 + +좀비가 먹이를 먹을 때 먹이는 바이러스에 감염되게 되지. 이 바이러스는 먹이를 새로운 좀비로 바꾸어 좀비 군대의 일원이 되도록 하지. 새로운 좀비의 DNA는 이전 좀비의 DNA와 먹이의 DNA를 활용하여 계산될 것이네. + +그럼 우리 좀비들이 가장 좋아하는 먹이가 무엇일까? + +그걸 알고 싶으면... 레슨 2를 마쳐야 할 것이네! + +# 직접 해보기 + +오른쪽에 보면 좀비가 먹이를 먹는 간단한 데모가 있지. 인간을 클릭해서 좀비가 먹이를 먹을 때 어떤 일이 일어나는지 보게! + +새로운 좀비의 DNA가 원래 좀비의 DNA와 먹이의 DNA에 의해 결정된다는 것을 알 수 있지. + +준비가 되면, "다음 챕터"를 클릭해서 계속 진행하게. 우리 게임을 멀티 플레이어 게임으로 만드는 것부터 시작하도록 하지. diff --git a/ko/2/10-interactingcontracts.md b/ko/2/10-interactingcontracts.md new file mode 100644 index 000000000..45e46a359 --- /dev/null +++ b/ko/2/10-interactingcontracts.md @@ -0,0 +1,193 @@ +--- +title: 좀비가 무엇을 먹나요? +actions: ['정답 확인하기', '힌트 보기'] +material: + editor: + language: sol + startingCode: + "zombiefeeding.sol": | + pragma solidity ^0.4.19; + + import "./zombiefactory.sol"; + + // 여기에 KittyInterface를 생성한다 + + contract ZombieFeeding is ZombieFactory { + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + _createZombie("NoName", newDna); + } + + } + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + 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; + 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); + _createZombie(_name, randDna); + } + + } + answer: > + 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 { + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + _createZombie("NoName", newDna); + } + + } +--- + +이제 좀비들에게 먹이를 줄 시간이군! 좀비가 가장 좋아하는 먹이가 뭘까? + +크립토좀비가 가장 좋아하는 먹이는... + +**크립토키티!** 😱😱😱 + +(그래, 정말이라네 😆 ) + +좀비에게 크립토키티를 먹이로 주려면 크립토키티 스마트 컨트렉트에서 키티 DNA를 읽어와야 할 것이네. 이게 가능한 이유는 크립토키티 데이터가 블록체인 상에 공개적으로 저장되어 있기 때문이지. 블록체인이 환상적이지 않나?! + +걱정 말게 - 우리 게임이 어느 누구의 크립토키티에게도 실제 해를 끼치지 않을 것이니 말일세. 우린 단지 크립토키티 데이터를 *읽어 올* 뿐이지. 실제로 이 데이터를 지울 수는 없다네. 😉 + +## 다른 컨트렉트와 상호작용하기 + +블록체인 상에 있으면서 우리가 소유하지 않은 컨트렉트와 우리 컨트렉트가 상호작용을 하려면 우선 **_인터페이스_**를 정의해야 하네. + +간단한 예시를 살펴 보도록 하지. 다음과 같은 블록체인 컨트렉트가 있다고 해 보세: + +``` +contract LuckyNumber { + mapping(address => uint) numbers; + + function setNum(uint _num) public { + numbers[msg.sender] = _num; + } + + function getNum(address _myAddress) public view returns (uint) { + return numbers[_myAddress]; + } +} +``` + +이 컨트렉트는 아무나 자신의 행운의 수를 저장할 수 있는 간단한 컨트렉트이고, 각자의 이더리움 주소와 연관이 있을 것이네. 이 주소를 이용해서 누구나 그 사람의 행운의 수를 찾아 볼 수 있지. + +이제 `getNum` 함수를 이용하여 이 컨트렉트에 있는 데이터를 읽고자 하는 external 함수가 있다고 해 보세. + +먼저, `LuckyNumber` 컨트렉트의 **_인터페이스_**를 정의할 필요가 있네: + +``` +contract NumberInterface { + function getNum(address _myAddress) public view returns (uint); +} +``` + +약간 다르지만, 인터페이스를 정의하는 것이 컨트렉트를 정의하는 것과 유사하다는 걸 참고하게. 먼저, 다른 컨트렉트와 상호작용하고자 하는 함수만을 선언할 뿐(이 경우, `getNum`이 바로 그러한 함수이지) 다른 함수나 상태 변수를 언급하지 않네. . + +다음으로, 함수 몸체를 정의하지 않지. 중괄호 `{`, `}`를 쓰지 않고 함수 선언을 세미콜론(`;`)으로 간단하게 끝내지. + +그러니 인터페이스는 컨트렉트 뼈대처럼 보인다고 할 수 있지. 컴파일러도 그렇게 인터페이스를 인식하지. + +우리의 댑 코드에 이런 인터페이스를 포함하면 컨트렉트는 다른 컨트렉트에 정의된 함수의 특성, 호출 방법, 예상되는 응답 내용에 대해 알 수 있게 되지. + +다음 레슨에서 다른 컨트렉트의 함수를 실제로 호출할 것일세. 지금은 크립토키티 컨트렉트를 위한 인터페이스를 선언해 보세. + +# 직접 해보기 + +자네를 위해 크립토키티 소스 코드를 찾아 봤네. 거기서, (우리 좀비 게임에서 새로운 좀비를 생성하는 데 필요한!) "유전자"를 포함한 모든 키티 데이터를 반환하는 `getKitty`라는 함수를 발견했네. + +`getKitty` 함수는 다음과 같네: + +``` +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 +) { + Kitty storage kit = kitties[_id]; + + // if this variable is 0 then it's not gestating + isGestating = (kit.siringWithId != 0); + isReady = (kit.cooldownEndBlock <= block.number); + cooldownIndex = uint256(kit.cooldownIndex); + nextActionAt = uint256(kit.cooldownEndBlock); + siringWithId = uint256(kit.siringWithId); + birthTime = uint256(kit.birthTime); + matronId = uint256(kit.matronId); + sireId = uint256(kit.sireId); + generation = uint256(kit.generation); + genes = kit.genes; +} +``` + +이 함수는 우리에게 익숙한 함수들과는 달라 보이지. 함수가 다양한 값들을 반환하고 있지... 자네가 자바스크립트 같은 프로그래밍 언어를 이용해 본 적이 있다면 이 점이 다르다는 걸 알 수 있을 거네. 솔리디티에서는 함수가 하나 이상의 값을 반환할 수 있지. + +`getKitty` 함수가 어떤 함수인지 알아 보았으니, 이를 이용하여 인터페이스를 만들어 볼 수 있을 걸세: + +1. `KittyInterface`라는 인터페이스를 정의한다. 인터페이스 정의가 `contract` 키워드를 이용하여 새로운 컨트렉트를 생성하는 것과 같다는 점을 기억할 것. + +2. 인터페이스 내에 `getKitty` 함수를 선언한다 (위의 함수에서 중괄호 안의 모든 내용은 제외하고 `return` 키워드 및 반환 값 종류까지만 복사/붙여넣기 하고 그 다음에 세미콜론을 넣어야 한다). diff --git a/ko/2/11-interactingcontracts2.md b/ko/2/11-interactingcontracts2.md new file mode 100644 index 000000000..5e9fd642a --- /dev/null +++ b/ko/2/11-interactingcontracts2.md @@ -0,0 +1,148 @@ +--- +title: 인터페이스 활용하기 +actions: ['정답 확인하기', '힌트 보기'] +material: + editor: + language: sol + startingCode: + "zombiefeeding.sol": | + 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 { + + address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; + // `ckAddress`를 이용하여 여기에 kittyContract를 초기화한다 + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + _createZombie("NoName", newDna); + } + + } + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + 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; + 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); + _createZombie(_name, randDna); + } + + } + answer: > + 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 { + + address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; + KittyInterface kittyContract = KittyInterface(ckAddress); + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + _createZombie("NoName", newDna); + } + + } +--- + +이전 챕터의 예시였던 `NumberInterface`를 활용하여 설명을 이어 나가겠네. 아래와 같이 인터페이스가 정의되면: + +``` +contract NumberInterface { + function getNum(address _myAddress) public view returns (uint); +} +``` + +다음과 같이 컨트렉트에서 인터페이스를 이용할 수 있지: + +``` +contract MyContract { + address NumberInterfaceAddress = 0xab38... + // ^ 이더리움 상의 FavoriteNumber 컨트렉트 주소이다 + NumberInterface numberContract = NumberInterface(NumberInterfaceAddress) + // 이제 `numberContract`는 다른 컨트렉트를 가리키고 있다. Now `numberContract` is pointing to the other contract + + function someFunction() public { + // 이제 `numberContract`가 가리키고 있는 컨트렉트에서 `getNum` 함수를 호출할 수 있다: + uint num = numberContract.getNum(msg.sender); + // ...그리고 여기서 `num`으로 무언가를 할 수 있다 + } +} +``` + +이런 식으로 자네의 컨트렉트가 이더리움 블록체인 상의 다른 어떤 컨트렉트와도 상호작용할 수 있네. 물론 상호작용하는 함수가 `public`이나 `external`로 선언되어 있어야 하지. + +# 직접 해보기 + +크립토키티 스마트 컨트렉트에서 데이터를 읽어 오도록 우리 컨트렉트를 설정해 보세! + +1. 코드를 보면 `ckAddress`라는 변수에 크립토키티 컨트렉트 주소가 입력되어 있다. 다음 줄에 `kittyContract`라는 `KittyInterface`를 생성하고, 위의 `numberContract` 선언 시와 동일하게 `ckAddress`를 이용하여 초기화한다. \ No newline at end of file diff --git a/ko/2/12-multiplereturns.md b/ko/2/12-multiplereturns.md new file mode 100644 index 000000000..4961c444f --- /dev/null +++ b/ko/2/12-multiplereturns.md @@ -0,0 +1,162 @@ +--- +title: 다수의 반환값 처리하기 +actions: ['정답 확인하기', '힌트 보기'] +material: + editor: + language: sol + startingCode: + "zombiefeeding.sol": | + 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 { + + address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; + KittyInterface kittyContract = KittyInterface(ckAddress); + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + _createZombie("NoName", newDna); + } + + // 여기에 함수를 정의 + + } + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + 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; + 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); + _createZombie(_name, randDna); + } + + } + answer: > + 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 { + + address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; + KittyInterface kittyContract = KittyInterface(ckAddress); + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + _createZombie("NoName", newDna); + } + + function feedOnKitty(uint _zombieId, uint _kittyId) public { + uint kittyDna; + (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId); + feedAndMultiply(_zombieId, kittyDna); + } + + } +--- + +`getKitty` 함수는 우리가 살펴 본 예시 중 유일하게 다수의 반환값을 갖는 함수이지. 본 챕터에서는 어떻게 다수의 반환값을 처리하는지 살펴 보세: T + +``` +function multipleReturns() internal returns(uint a, uint b, uint c) { + return (1, 2, 3); +} + +function processMultipleReturns() external { + uint a; + uint b; + uint c; + // 다음과 같이 다수 값을 할당한다: + (a, b, c) = multipleReturns(); +} + +// 혹은 단 하나의 값에만 관심이 있을 경우: +function getLastReturnValue() external { + uint c; + // 다른 필드는 빈칸으로 놓기만 하면 된다: + (,,c) = multipleReturns(); +} +``` + +# 직접 해보기 + +이제 크립토키티 컨트렉트와 상호작용할 시간이네! + +크립토키티 컨트렉트에서 고양이 유전자를 얻어내는 함수를 생성해 보세: + +1. `feedOnKitty`라는 함수를 생성한다. 이 함수는 `_zombieId`와 `_kittyId`라는 `uint` 인자 값 2개를 전달받고, `public` 함수로 선언되어야 한다. + +2. 이 함수는 `kittyDna`라는 `uint`를 먼저 선언해야 한다. + + > 참고: `KittyInterface` 인터페이스에서 `gene`은 `uint256`형이지만, 레슨 1에서 배웠던 내용을 되새겨 보면 `uint`는 `uint256`의 다른 표현으로, 서로 동일하지. + +3. 그 다음, 이 함수는 `_kittyID`를 전달하여 `kittyContract.getKitty` 함수를 호출하고 `gene`을 `kittyDna`에 저장해야 한다.`getKitty`가 다수의 변수를 반환한다는 사실을 기억할 것 (정확히 말하자면 10개의 변수를 반환한다). 하지만 우리가 관심 있는 변수는 마지막 변수인 `gene`이다. 쉼표 수를 유심히 세어 보기 바란다! + +4. 마지막으로 이 함수는 `feedAndMultiply`를 호출하고 이 때 `_zombieId`와 `kittyDna`를 전달해야 한다. \ No newline at end of file diff --git a/ko/2/13-kittygenes.md b/ko/2/13-kittygenes.md new file mode 100644 index 000000000..4c07d8e34 --- /dev/null +++ b/ko/2/13-kittygenes.md @@ -0,0 +1,170 @@ +--- +title: "보너스: 키티 유전자" +actions: ['정답 확인하기', '힌트 보기'] +material: + editor: + language: sol + startingCode: + "zombiefeeding.sol": | + 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 { + + address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; + KittyInterface kittyContract = KittyInterface(ckAddress); + + // 여기에 있는 함수 정의를 변경: + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + // 여기에 if 문 추가 + _createZombie("NoName", newDna); + } + + function feedOnKitty(uint _zombieId, uint _kittyId) public { + uint kittyDna; + (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId); + // 여기에 있는 함수 호출을 변경: + feedAndMultiply(_zombieId, kittyDna); + } + + } + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + 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; + 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); + } + + } + answer: > + 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 { + + address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; + KittyInterface kittyContract = KittyInterface(ckAddress); + + function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + if (keccak256(_species) == keccak256("kitty")) { + newDna = newDna - newDna % 100 + 99; + } + _createZombie("NoName", newDna); + } + + function feedOnKitty(uint _zombieId, uint _kittyId) public { + uint kittyDna; + (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId); + feedAndMultiply(_zombieId, kittyDna, "kitty"); + } + + } +--- + +우리의 함수 로직이 이제 완료되었군... 하지만 한 가지를 보너스로 추가해 보도록 하세. + +고양이 유전자와 조합되어 생성된 좀비가 몇 가지 독특한 특성을 가져서 고양이좀비로 보이도록 해 보세. + +이를 위해 좀비 DNA에 몇 가지 특별한 키티 코드를 추가할 수 있네. + +레슨 1에서 배운 내용을 떠올려 보면, 좀비의 외모를 결정하는 데 있어서 16자리 DNA 중에서 처음 12자리만 이용되지. 그러니 마지막에서 2자리 숫자를 활용하여 "스페셜한" 특성을 만들어 보세. + +고양이좀비는 DNA 마지막 2자리로 `99`를 갖는다고 해 보세 (고양이는 9개의 목숨을 가졌다고 할 만큼 생명력이 강하므로). 그러면 우리 코드에서는 만약(`if`) 좀비가 고양이에서 생성되면 좀비 DNA의 마지막 2자리를 `99`로 설정한다. + +## If 문 + +솔리디티에서 if 문은 자바스크립트의 if 문과 동일하다: +``` +function eatBLT(string sandwich) public { + // 스트링 간의 동일 여부를 판단하기 위해 keccak256 해시 함수를 이용해야 한다는 것을 기억하자 + if (keccak256(sandwich) == keccak256("BLT")) { + eat(); + } +} +``` + +# 직접 해보기 + +우리의 좀비 코드에 고양이 유전자에 대한 내용을 구현해 보세. + +1. 먼저, `feedAndMultiply` 함수 정의를 변경하여 `_species`라는 `string`을 세번째 인자 값으로 전달받도록 한다. + +2. 그 다음, 새로운 좀비 DNA를 계산한 후에 `if` 문을 추가하여 `_species`와 `"kitty"` 스트링 각각의 `keccak256` 해시값을 비교하도록 한다. + +3. `if` 문 내에서 DNA 마지막 2자리를 `99`로 대체하고자 한다. 한가지 방법은 `newDna = newDna - newDna % 100 + 99;` 로직을 이용하는 것이다. + + > 설명: `newDna`가 `334455`라고 하면 `newDna % 100`는 `55`이고, 따라서 `newDna - newDna % 100`는 `334400`이다. 마지막으로 여기에 `99`를 더하면 `334499`를 얻게 된다. + +4. 마지막으로, `feedOnKitty` 함수 내에서 이뤄지는 함수 호출을 변경해야 한다. `feedAndMultiply`가 호출될 때, `"kitty"`를 마지막 인자값으로 전달한다. \ No newline at end of file diff --git a/ko/2/14-wrappingitup.md b/ko/2/14-wrappingitup.md new file mode 100644 index 000000000..870750909 --- /dev/null +++ b/ko/2/14-wrappingitup.md @@ -0,0 +1,64 @@ +--- +title: 마무리하기 Wrapping It Up +actions: ['정답 확인하기', '힌트 보기'] +material: + saveZombie: true + zombieBattle: + zombie: + lesson: 1 + humanBattle: false + hideSliders: true + answer: 1 +--- + +그게 전부네. 자네가 레슨 2를 완료했네! + +코드가 작동하는지 오른편에서 데모를 확인해 볼 수 있네. 자네가 얼른 이 페이지 하단으로 가고 싶어한다는 걸 아네 😉. 고양이를 클릭해서 공격하고 나서 새로운 고양이좀비가 어떤지 보게! + +## 자바스크립트를 활용한 구현 + +우리 컨트렉트를 이더리움에 구축할 준비가 되면 `ZombieFeeding` 컨트렉트만 컴파일해서 구축하면 될 것일세. 왜냐면 이 컨트렉트가 `ZombieFactory`를 상속하는 우리의 마지막 컨트렉트이고 두 컨트렉트에 있는 public 메소드를 모두 접근할 수 있기 때문이지. + +자바스크립트와 web3.js를 활용하여 우리의 컨트렉트와 상호작용하는 예시를 살펴 보도록 하지: + +``` +var abi = /* abi generated by the compiler */ +var ZombieFeedingContract = web3.eth.contract(abi) +var contractAddress = /* our contract address on Ethereum after deploying */ +var ZombieFeeding = ZombieFeedingContract.at(contractAddress) + +// 우리 좀비의 ID와 타겟 고양이 ID를 가지고 있다고 가정하면 +let zombieId = 1; +let kittyId = 1; + +// 크립토키티의 이미지를 얻기 위해 웹 API에 쿼리를 할 필요가 있다. +// 이 정보는 블록체인이 아닌 크립토키티 웹 서버에 저장되어 있다. +// 모든 것이 블록체인에 저장되어 있으면 서버가 다운되거나 크립토키티 API가 바뀌는 것이나 +// 크립토키티 회사가 크립토좀비를 싫어해서 고양이 이미지를 로딩하는 걸 막는 등을 걱정할 필요가 없다 ;) +let apiUrl = "https://api.cryptokitties.co/kitties/" + kittyId +$.get(apiUrl, function(data) { + let imgUrl = data.image_url + // 이미지를 제시하기 위해 무언가를 한다 +}) + +// 유저가 고양이를 클릭할 때: +$(".kittyImage").click(function(e) { + // 우리 컨트렉트의 `feedOnKitty` 메소드를 호출한다 + ZombieFeeding.feedOnKitty(zombieId, kittyId) +}) + +// 우리의 컨트렉트에서 발생 가능한 NewZombie 이벤트에 귀를 기울여서 이벤트 발생 시 이벤트를 제시할 수 있도록 한다: +ZombieFactory.NewZombie(function(error, result) { + if (error) return + // 이 함수는 레슨 1에서와 같이 좀비를 제시한다: + generateZombie(result.zombieId, result.name, result.dna) +}) +``` + +# 직접 해보기! + +좀비에게 먹일 고양이를 선택해 보게. 자네 좀비의 DNA와 고양이의 DNA가 조합되어 새로운 좀비를 얻을 수 있을 거네! + +자네의 새로운 좀비가 귀여운 고양이 다리를 가진 게 보이나? 저게 바로 DNA의 마지막 2자리 `99`가 작동하고 있다는 증거지. 😉 + +원하면 다시 시작해서 좀비에게 고양이를 먹여 보게. 자네 마음에 드는 고양이좀비를 얻으면 (한 마리만 가질 수 있네) 다음 챕터로 이동해서 레슨 2를 완료하게! \ No newline at end of file diff --git a/ko/2/15-lessoncomplete.md b/ko/2/15-lessoncomplete.md new file mode 100644 index 000000000..bb727dade --- /dev/null +++ b/ko/2/15-lessoncomplete.md @@ -0,0 +1,8 @@ +--- +title: 레슨 2 완료! +actions: ['정답 확인하기', '힌트 보기'] +material: + lessonComplete: + answer: 1 +--- + diff --git a/ko/2/2-mappings.md b/ko/2/2-mappings.md new file mode 100644 index 000000000..bf1bf8700 --- /dev/null +++ b/ko/2/2-mappings.md @@ -0,0 +1,120 @@ +--- +title: 매핑과 주소 +actions: ['정답 확인하기', '힌트 보기'] +material: + editor: + language: sol + startingCode: | + pragma solidity ^0.4.19; + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + Zombie[] public zombies; + + // 여기서 매핑 선언 + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 1; + 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 { + uint randDna = _generateRandomDna(_name); + _createZombie(_name, randDna); + } + + } + answer: > + pragma solidity ^0.4.19; + + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + Zombie[] public zombies; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 1; + 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 { + uint randDna = _generateRandomDna(_name); + _createZombie(_name, randDna); + } + + } +--- + +데이터베이스에 저장된 좀비들에게 주인을 설정하여 우리 게임을 멀티 플레이어 게임으로 만들어 보세. + +이걸 하려면 `mapping`과 `address`라는 2가지 새로운 자료형이 필요할 거네. + +## 주소 + +이더리움 블록체인은 은행 계좌와 같은 **_계정들_**로 이루어져 있지. 계정은 이더리움 블록체인 상의 통화인 **_이더_**의 잔액을 가지지. 자네의 은행 계좌를 통해 다른 계좌로 돈을 송금할 수 있듯이, 계정을 통해 다른 계정과 이더를 주고 받을 수 있지. + +각 계정은 은행 계좌 번호와 같은 `주소`를 가지고 있네. 주소는 특정 계정을 가리키는 고유 식별자로, 다음과 같이 표현되지: + +`0x0cE446255506E92DF41614C46F1d6df9Cc969183` + +(이 주소는 크립토좀비 팀의 주소지. 자네가 크립토좀비를 즐기고 있다면 우리에게 이더 몇 개를 보내줄 수 있겠지! 😉) T + +이후 레슨에서 주소에 관한 핵심 내용을 알아 볼 것일세. 지금은 자네가 **"주소는 특정 유저(혹은 스마트 컨트렉트)가 소유한다"**라는 점만 이해할 필요가 있네. + +그러니까 주소를 우리 좀비들에 대한 소유권을 나타내는 고유 ID로 활용할 수 있네. 유저가 우리 앱과 상호작용해서 새로운 좀비를 생성하면 생성 함수를 호출한 이더리움 주소에 그 좀비에 대한 소유권을 부여하지. + +## 매핑 + +레슨 1에서 **_구조체_**와 **_배열_**를 살펴 봤네. **_매핑_**은 솔리디티에서 구조화된 데이터를 저장하는 또다른 방법이지. + +다음과 같이 `매핑`을 정의하지: + +``` +// 금융 앱용으로, 유저의 계좌 잔액을 보유하는 uint를 저장한다: +mapping (address => uint) public accountBalance; +// 혹은 userID을 기초로 유저 이름을 저장/검색하는 데 매핑을 쓸 수도 있다 +mapping (uint => string) userIdToName; +``` + +매핑은 기본적으로 키-값 (key-value) 저장소로, 데이터를 저장하고 검색하는 데 이용된다. 첫번째 예시에서 키는 `address`이고 값은 `uint`이다. 두번째 예시에서 키는 `uint`이고 값은 `string`이다. + +# 직접 해보기 + +좀비 소유권을 저장하기 위해 2가지 매핑을 이용하고자 하네: 하나는 좀비 소유자의 주소를 추적하기 위한 것이고, 다른 하나는 소유한 좀비의 숫자를 추적하기 위한 것이네. + +1. `zombieToOwner`라는 매핑을 생성한다. 키는 `uint`이고 (좀비 ID에 기초하여 좀비를 저장하고 검색할 것이다), 값은 `address`이다. 이 매핑을 `public`으로 설정하자. + +2. `ownerZombieCount`라는 매핑을 설정한다. 키는 `address`이고 값은 `uint`이다. \ No newline at end of file diff --git a/ko/2/3-msgsender.md b/ko/2/3-msgsender.md new file mode 100644 index 000000000..dad166e1d --- /dev/null +++ b/ko/2/3-msgsender.md @@ -0,0 +1,133 @@ +--- +title: Msg.sender +actions: ['정답 확인하기', '힌트 보기'] +material: + editor: + language: sol + startingCode: | + pragma solidity ^0.4.19; + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + Zombie[] public zombies; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 1; + // 여기서 시작 + 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 { + uint randDna = _generateRandomDna(_name); + _createZombie(_name, randDna); + } + + } + answer: > + pragma solidity ^0.4.19; + + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + Zombie[] public zombies; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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 { + uint randDna = _generateRandomDna(_name); + _createZombie(_name, randDna); + } + + } +--- + +좀비 소유자를 추적하는 매핑을 가지고 있으니 `_createZombie` 메소드를 업데이트해서 이 매핑을 이용하도록 하고 싶네. + +이를 위해, `msg.sender`이라는 것을 이용할 필요가 있네. + +## msg.sender + +솔리디티에는 모든 함수에서 이용 가능한 특정 전역 변수들이 있지. 그 중의 하나가 현재 함수를 호출한 사람 (혹은 스마트 컨트렉트)의 주소를 가리키는 `msg.sender`이지. + +> 참고: 솔리디티에서 함수 실행은 항상 외부 호출자가 시작하네. 컨트렉트는 누군가가 컨트렉트의 함수를 호출할 때까지 블록체인 상에서 아무 것도 안 하고 있을 거네. 그러니 항상 `msg.sender`가 있어야 하네. + +`msg.sender`를 이용하고 `mapping`을 업데이트하는 예시가 여기에 있네: + +``` +mapping (address => uint) favoriteNumber; + +function setMyNumber(uint _myNumber) public { + // `msg.sender`에 대해 `_myNumber`가 저장되도록 `favoriteNumber` 매핑을 업데이트한다 ` + favoriteNumber[msg.sender] = _myNumber; + // ^ 데이터를 저장하는 구문은 배열로 데이터를 저장할 떄와 동일하다 +} + +function whatIsMyNumber() public view returns (uint) { + // sender의 주소에 저장된 값을 불러온다 + // sender가 `setMyNumber`을 아직 호출하지 않았다면 반환값은 `0`이 될 것이다 + return favoriteNumber[msg.sender]; +} +``` + +이 간단한 예시에서 누구나 `setMyNumber`을 호출하여 본인의 주소와 연결된 우리 컨트렉트 내에 `uint`를 저장할 수 있지. + +`msg.sender`를 활용하면 자네가 이더리움 블록체인의 보안성을 이용할 수 있게 되지. 즉, 누군가가 다른 사람의 데이터를 변경할 수 있으려면 해당 이더리움 주소와 관련된 개인키를 훔치는 것 밖에는 다른 방법이 없지. + +# 직접 해보기 + +레슨 1에서 다뤘던 `_createZombie` 메소드를 업데이트하여 이 함수를 호출하는 누구나에게 좀비 소유권을 부여하도록 해 보세. + +1. 먼저, 새로운 좀비의 `id`가 반환된 후에 `zombieToOwner` 매핑을 업데이트하여 `id`에 대하여 `msg.sender`가 저장되도록 해보자. + +2. 그 다음, 저장된 `msg.sender`을 고려하여 `ownerZombieCount`를 증가시키자. + +자바스크립트와 마찬가지로 솔리디티에서도 `uint`를 `++`로 증가시킬 수 있다. + +``` +uint number = 0; +number++; +// `number`는 이제 `1`이다 +``` + +자네의 최종 답안은 코드 2줄로 표현되어야 하네. \ No newline at end of file diff --git a/ko/2/4-require.md b/ko/2/4-require.md new file mode 100644 index 000000000..b17088e30 --- /dev/null +++ b/ko/2/4-require.md @@ -0,0 +1,119 @@ +--- +title: Require +actions: ['정답 확인하기', '힌트 보기'] +material: + editor: + language: sol + startingCode: | + pragma solidity ^0.4.19; + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + Zombie[] public zombies; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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 { + // 여기서 시작 + uint randDna = _generateRandomDna(_name); + _createZombie(_name, randDna); + } + + } + answer: > + pragma solidity ^0.4.19; + + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + Zombie[] public zombies; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } +--- + +레슨 1에서 유저가 `createRandomZombie`를 호출하여 좀비 이름을 입력하면 새로운 좀비를 생성할 수 있도록 했네. 하지만, 만일 유저가 이 함수를 계속 호출해서 무제한으로 좀비를 생성한다면 게임이 매우 재미있지는 않을 걸세. + +각 플레이어가 이 함수를 한 번만 호출할 수 있도록 만들어 보세. 이로써 새로운 플레이어들이 게임을 처음 시작할 때 좀비 군대를 구성할 첫 좀비를 생성하기 위해 `createRandomZombie`함수를 호출하게 될 것이네. + +어떻게 하면 이 함수가 각 플레이어마다 한 번씩만 호출되도록 할 수 있을까? + +이를 위해 `require`를 활용할 것이네. `require`를 활용하면 특정 조건이 참이 아닐 때 함수가 에러 메시지를 발생하고 실행을 멈추게 되지: + +``` +function sayHiToVitalik(string _name) public returns (string) { + // _name이 "Vitalik"인지 비교한다. 참이 아닐 경우 에러 메시지를 발생하고 함수를 벗어난다 + // (참고: 솔리디티는 고유의 스트링 비교 기능을 가지고 있지 않기 때문에 + // 스트링의 keccak256 해시값을 비교하여 스트링 값이 같은지 판단한다) + require(keccak256(_name) == keccak256("Vitalik")); + // 참이면 함수 실행을 진행한다: + return "Hi!"; +} +``` + +`sayHiToVitalik("Vitalik")`로 이 함수를 실행하면 "Hi!"가 반환될 것이네. "Vitalik"이 아닌 다른 값으로 이 함수를 호출할 경우, 에러 메시지가 뜨고 함수가 실행되지 않을 걸세. + +그러므로 `require`는 함수를 실행하기 전에 참이어야 하는 특정 조건을 확인하는 데 있어서 꽤 유용하지. + +# 직접 해보기 + +우리의 좀비 게임에서 유저가 `createRandomZombie` 함수를 반복적으로 호출해서 자신의 군대에 좀비를 무제한으로 생성하는 것을 원하지 않네. 그렇게 되면 게임이 재미없게 될 걸세. + +`require`를 활용하여 유저들이 첫 좀비를 만들 때 이 함수가 유저 당 한 번만 호출되도록 해 보세. + +1. `require` 키워드를 `createRandomZombie` 앞부분에 입력한다. `require` 함수가 `ownerZombieCount[msg.sender]`이 0과 같은지 확인하도록 하고, 0이 아닌 경우 에러 메시지를 출력하도록 한다. + +> 참고: 솔리디티에서 값을 비교할 때 어떤 항이 먼저 오느냐는 중요하지 않네. 어떤 순서든지 동일하지. 하지만, 우리가 작성한 확인 기능은 매우 기본적이라서 한 가지 값만을 참이라고 하네. 그러니 `ownerZombieCount[msg.sender]`이 가장 먼저 올 것이 기대되지. \ No newline at end of file diff --git a/ko/2/5-inheritance.md b/ko/2/5-inheritance.md new file mode 100644 index 000000000..a82f18b2f --- /dev/null +++ b/ko/2/5-inheritance.md @@ -0,0 +1,122 @@ +--- +title: 상속 +actions: ['정답 확인하기', '힌트 보기'] +material: + editor: + language: sol + startingCode: | + pragma solidity ^0.4.19; + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + Zombie[] public zombies; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } + + // 여기서 시작 + + answer: > + pragma solidity ^0.4.19; + + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + Zombie[] public zombies; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } + + contract ZombieFeeding is ZombieFactory { + + } + +--- + +우리의 게임 코드가 점점 더 꽤 길어지고 있군. 엄청나게 긴 컨트렉트 하나를 만들기 보다는 코드를 잘 정리해서 여러 컨트렉트에 코드 로직을 나누는 것이 합리적일 때가 있지. + +이를 보다 관리하기 쉽도록 하는 솔리디티 기능이 바로 컨트렉트 **_상속_**이지: + +``` +contract Doge { + function catchphrase() public returns (string) { + return "So Wow CryptoDoge"; + } +} + +contract BabyDoge is Doge { + function anotherCatchphrase() public returns (string) { + return "Such Moon BabyDoge"; + } +} +``` + +`BabyDoge` 컨트렉트는 `Doge` 컨트렉트를 상속하네. 즉, 자네가 `BabyDoge` 컨트렉트를 컴파일해서 구축할 때, `BabyDoge` 컨트렉트가 `catchphrase()` 함수와 `anotherCatchphrase()` 함수에 모두 접근할 수 있다는 뜻이지. (`Doge` 컨트렉트에 정의되는 다른 어떤 public 함수가 정의되어도 접근이 가능하네) + +상속 개념은 "`고양이`는 `동물`이다"의 경우처럼 부분집합 클래스가 있을 때 논리적 상속을 위해 활용할 수 있지. 하지만 동일한 로직을 다수의 클래스로 분할해서 단순히 코드를 정리할 때도 활용할 수 있지. + +# 직접 해보기 + +다음 챕터에서 우리 좀비들이 먹이를 먹고 번식하도록 하는 기능을 구현할 것일세. 그 기능의 로직을 `ZombieFactory`의 모든 메소드를 상속하는 클래스에 넣어 보도록 하세. + +1. `ZombieFactory` 아래에 `ZombieFeeding` 컨트렉트르 생성한다. 이 컨트렉트는 `ZombieFactory`를 상속해야 한다. diff --git a/ko/2/6-importfiles.md b/ko/2/6-importfiles.md new file mode 100644 index 000000000..f17c020a7 --- /dev/null +++ b/ko/2/6-importfiles.md @@ -0,0 +1,86 @@ +--- +title: Import +actions: ['정답 확인하기', '힌트 보기'] +material: + editor: + language: sol + startingCode: + "zombiefeeding.sol": | + pragma solidity ^0.4.19; + + // 여기에 import 구문을 넣기 + + contract ZombieFeeding is ZombieFactory { + + } + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + Zombie[] public zombies; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } + answer: > + pragma solidity ^0.4.19; + + import "./zombiefactory.sol"; + + contract ZombieFeeding is ZombieFactory { + + } + +--- + +와우! 우리가 방금 코드를 오른편으로 정리했다는 걸 알 수 있을 걸세. 이제 에디터의 상단부에 탭이 있네. 탭을 클릭해서 살펴보도록 하게. You'll notice we just cleaned up the code to the right, and you now have tabs at the top of your editor. Go ahead, click between the tabs to try it out. + +우리 코드가 꽤 길어지고 있으니, 여러 파일로 나누어 정리하면 더 관리하기 편하겠지. 보통 이런 방식으로 솔리디티 프로젝트의 긴 코드를 처리할 것이네. + +다수의 파일이 있고 어떤 파일을 다른 파일로 불러오고 싶을 때, 솔리디티는 `import`라는 키워드를 이용하지: + +``` +import "./someothercontract.sol"; + +contract newContract is SomeOtherContract { + +} +``` + +이 컨트렉트와 동일한 폴더에 (이게 `./`가 의미하는 바임) `someothercontract.sol`이라는 파일이 있을 때, 이 파일을 컴파일러가 불러오게 되지. + +# 직접 해보기 + +다수의 파일이 있는 구조를 갖추었으니 `import`를 활용하여 다른 파일의 내용을 읽어올 필요가 있네. + +1. 새로운 파일 `zombiefeeding.sol`에 `zombiefactory.sol`를 불러 온다(`import`). diff --git a/ko/2/7-storage.md b/ko/2/7-storage.md new file mode 100644 index 000000000..e18d72e60 --- /dev/null +++ b/ko/2/7-storage.md @@ -0,0 +1,134 @@ +--- +title: Storage vs Memory +actions: ['정답 확인하기', '힌트 보기'] +material: + editor: + language: sol + startingCode: + "zombiefeeding.sol": | + pragma solidity ^0.4.19; + + import "./zombiefactory.sol"; + + contract ZombieFeeding is ZombieFactory { + + // 여기서 시작 + + } + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + Zombie[] public zombies; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } + answer: > + pragma solidity ^0.4.19; + + import "./zombiefactory.sol"; + + contract ZombieFeeding is ZombieFactory { + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + } + + } +--- + +솔리디티에는 변수를 저장할 수 있는 공간으로 `storage`와 `memory` 두 가지가 있지. + +**_Storage_**는 블록체인 상에 영구적으로 저장되는 변수를 의미하지. **_Memory_**는 임시적으로 저장되는 변수로, 컨트렉트 함수에 대한 외부 호출들이 일어나는 사이에 지워지지. 두 변수는 각각 컴퓨터 하드 디스크와 RAM과 같지. + +대부분의 경우에 자네는 이런 키워드들을 이용할 필요가 없네. 왜냐면 솔리디티가 알아서 처리해 주기 때문이지. 상태 변수(함수 외부에 선언된 변수)는 초기 설정상 `storage`로 선언되어 블록체인에 영구적으로 저장되는 반면, 함수 내에 선언된 변수는 `memory`로 자동 선언되어서 함수 호출이 종료되면 사라지지. + +하지만 이 키워드들을 사용해야 하는 때가 있지. 바로 함수 내의 **_구조체_**와 **_배열_**을 처리할 때지: + +``` +contract SandwichFactory { + struct Sandwich { + string name; + string status; + } + + Sandwich[] sandwiches; + + function eatSandwich(uint _index) public { + // Sandwich mySandwich = sandwiches[_index]; + + // ^ 꽤 간단해 보이나, 솔리디티는 여기서 + // `storage`나 `memory`를 명시적으로 선언해야 한다는 경고 메시지를 발생한다. + // 그러므로 `storage` 키워드를 활용하여 다음과 같이 선언해야 한다: + Sandwich storage mySandwich = sandwiches[_index]; + // ...이 경우, `mySandwich`는 저장된 `sandwiches[_index]`를 가리키는 포인터이다. + // 그리고 + mySandwich.status = "Eaten!"; + // ...이 코드는 블록체인 상에서 `sandwiches[_index]`을 영구적으로 변경한다. + + // 단순히 복사를 하고자 한다면 `memory`를 이용하면 된다: + Sandwich memory anotherSandwich = sandwiches[_index + 1]; + // ...이 경우, `anotherSandwich`는 단순히 메모리에 데이터를 복사하는 것이 된다. + // 그리고 + anotherSandwich.status = "Eaten!"; + // ...이 코드는 임시 변수인 `anotherSandwich`를 변경하는 것으로 + // `sandwiches[_index + 1]`에는 아무런 영향을 끼치지 않는다. 그러나 다음과 같이 코드를 작성할 수 있다: + sandwiches[_index + 1] = anotherSandwich; + // ...이는 임시 변경한 내용을 블록체인 저장소에 저장하고자 하는 경우이다. + } +} +``` + +어떤 키워드를 이용해야 하는지 정확하게 이해하지 못한다고 해도 걱정 말게. 이 튜토리얼을 진행하는 동안 언제 `storage` 혹은 `memory`를 사용해야 하는지 알려 주겠네. 솔리디티 컴파일러도 경고 메시지를 통해 어떤 키워드를 사용해야 하는지 알려 줄 것이네. + +지금으로선 명시적으로 `storage`나 `memory`를 선언할 필요가 있는 경우가 있다는 걸 이해하는 것만으로 충분하네! + +# 직접 해보기 + +먹이를 먹고 번식하는 능력을 우리 좀비들에게 부여할 시간이네! + +좀비가 어떤 다른 생명체를 잡아 먹을 때, 좀비 DNA가 생명체의 DNA와 혼합되어 새로운 좀비가 생성될 것이네. + +1. `feedAndMultiply`라는 함수를 생성한다. 이 함수는 `uint`형인 `_zombieId` 및 `_targetDna`을 전달받는다. 이 함수는 `public`으로 선언되어야 한다. + +2. 다른 누군가가 우리 좀비에게 먹이를 주는 것을 원치 않는다. 그러므로 주인만이 좀비에게 먹이를 줄 수 있도록 한다. `require` 구문을 추가하여 `msg.sender`가 좀비 주인과 동일하도록 한다. (이는 `createRandomZombie` 함수에서 쓰인 방법과 동일하다) + + > 참고: 다시 말하지만, 우리가 작성한 확인 기능은 기초적이기 때문에 컴파일러는 `msg.sender`가 먼저 나올 것을 기대하고, 항의 순서를 바꾸면 잘못된 값이 입력되었다고 할 걸세. 하지만 보통 코드를 작성할 때 항의 순서는 자네가 원하는 대로 정하면 되네. 어떤 경우든 참이 되거든. + +3. 먹이를 먹는 좀비 DNA를 얻을 필요가 있으므로, 그 다음으로 `myZombie`라는 `Zombie`형 변수를 선언한다 (이는 `storage` 포인터가 될 것이다). 이 변수에 `zombies` 배열의 `_zombieId` 인덱스가 가진 값에 부여한다. + +자네 코드는 마지막 `}`를 포함해서 4줄이어야 하네. + +다음 챕터에서 이 함수의 내용을 계속해서 작성할 걸세! \ No newline at end of file diff --git a/ko/2/8-feedandmultiply2.md b/ko/2/8-feedandmultiply2.md new file mode 100644 index 000000000..1c3778f06 --- /dev/null +++ b/ko/2/8-feedandmultiply2.md @@ -0,0 +1,106 @@ +--- +title: 좀비 DNA +actions: ['정답 확인하기', '힌트 보기'] +material: + editor: + language: sol + startingCode: + "zombiefeeding.sol": | + pragma solidity ^0.4.19; + + import "./zombiefactory.sol"; + + contract ZombieFeeding is ZombieFactory { + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + // 여기서 시작 + } + + } + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + Zombie[] public zombies; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } + answer: > + pragma solidity ^0.4.19; + + import "./zombiefactory.sol"; + + contract ZombieFeeding is ZombieFactory { + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + _createZombie("NoName", newDna); + } + + } +--- + +`feedAndMultiply` 함수 작성을 마무리해 보세! + +새로운 좀비의 DNA를 계산하는 공식은 간단하네: 먹이를 먹는 좀비의 DNA와 먹이의 DNA의 평균을 내는 거지. + +예시: + +``` +function testDnaSplicing() public { + uint zombieDna = 2222222222222222; + uint targetDna = 4444444444444444; + uint newZombieDna = (zombieDna + targetDna) / 2; + // ^ 3333333333333333이 될 것이다 +} +``` + +자네가 원한다면 나중에 공식을 좀더 복잡하게 할 수도 있을 거네. 하지만 지금으로선 공식을 간단하게 하도록 하지. 나중에 언제든지 변경할 수 있으니까. + +# 직접 해보기 + +1. 먼저, `_targetDna`가 16자리보다 크지 않도록 해야 한다. 이를 위해, `_targetDna`를 `_targetDna % dnaModulus`와 같도록 해서 마지막 16자리 수만 취하도록 한다. + +2. 그 다음, 함수가 `newDna`라는 `uint`를 선언하고 `myZombie`의 DNA와 `_targetDna`의 평균 값을 부여해야 한다. (위의 예시 참고) + + > 참고: `myZombie.name`와 `myZombie.dna`를 이용하여 `myZombie` 구조체의 변수에 접근할 수 있지. + +3. 새로운 DNA 값을 얻게 되면 `_createZombie` 함수를 호출한다. 이 함수를 호출하는 데 필요한 인자 값을 `zombiefactory.sol` 탭에서 확인할 수 있다. 참고로, 이 함수는 좀비의 이름을 인자 값으로 필요로 한다. 그러니 새로운 좀비의 이름을 현재로서는 "NoName"으로 하도록 하자. 나중에 좀비 이름을 변경하는 함수를 작성할 수 있을 것이다. + +> 참고: 솔리디티가 자네를 위해 열심히 일해서 자네가 코드의 문제점을 알아 차렸을 수도 있겠군. 걱정 말게. 다음 챕터에서 문제를 해결할 걸세 ;) diff --git a/ko/2/9-internalfunctions.md b/ko/2/9-internalfunctions.md new file mode 100644 index 000000000..af4de4e5f --- /dev/null +++ b/ko/2/9-internalfunctions.md @@ -0,0 +1,146 @@ +--- +title: 함수 접근 제어자 더 알아보기 +actions: ['정답 확인하기', '힌트 보기'] +material: + editor: + language: sol + startingCode: + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + Zombie[] public zombies; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + // edit function definition below + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } + "zombiefeeding.sol": | + pragma solidity ^0.4.19; + + import "./zombiefactory.sol"; + + contract ZombieFeeding is ZombieFactory { + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + _createZombie("NoName", newDna); + } + + } + answer: > + pragma solidity ^0.4.19; + + contract ZombieFactory { + + event NewZombie(uint zombieId, string name, uint dna); + + uint dnaDigits = 16; + uint dnaModulus = 10 ** dnaDigits; + + struct Zombie { + string name; + uint dna; + } + + 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; + 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); + _createZombie(_name, randDna); + } + + } +--- + +**지난 레슨의 코드에 실수가 있네!** + +자네가 코드를 컴파일하려고 하면 컴파일러가 에러 메시지를 출력할 거네. + +문제는 `ZombieFeeding` 컨트렉트 내에서 `_createZombie` 함수를 호출하려고 했다는 거지. 그런데 `_createZombie` 함수는 `ZombieFactory` 컨트렉트 내의 `private` 함수이지. 즉, `ZombieFactory` 컨트렉트를 상속하는 어떤 컨트렉트도 이 함수에 접근할 수 없다는 뜻이지. + +## Internal과 External + +`public`과 `private` 이외에도 솔리디티에는 `internal`과 `external`이라는 함수 접근 제어자가 있지. + +`internal`은 함수가 정의된 컨트렉트를 상속하는 컨트렉트에서도 접근이 가능하다 점을 제외하면 `private`과 동일하지. **(우리한테 필요한 게 바로 `internal`인 것 같군! + +`external`은 함수가 컨트렉트 바깥에서만 호출될 수 있고 컨트렉트 내의 다른 함수에 의해 호출될 수 없다는 점을 제외하면 `public`과 동일하지. 나중에 `external`과 `public`이 각각 왜 필요한지 살펴 볼 것이네. + +`interal`이나 `external` 함수를 선언하는 건 `private`과 `public` 함수를 선언하는 구문과 동일하네: + +``` +contract Sandwich { + uint private sandwichesEaten = 0; + + function eat() internal { + sandwichesEaten++; + } +} + +contract BLT is Sandwich { + uint private baconSandwichesEaten = 0; + + function eatWithBacon() public returns (string) { + baconSandwichesEaten++; + // eat 함수가 internal로 선언되었기 때문에 여기서 호출이 가능하다 + eat(); + } +} +``` + +# 직접 해보기 + +1. `_createZombie()` 함수를 `private`에서 `internal`로 바꾸어 선언하여 이 함수가 정의된 컨트렉트를 상속하는 컨트렉트에서도 접근 가능하도록 한다. + + 이미 `zombiefactory.sol` 탭이 활성화되어 있다. + \ No newline at end of file diff --git a/ko/2/template.md b/ko/2/template.md new file mode 100644 index 000000000..c62accc78 --- /dev/null +++ b/ko/2/template.md @@ -0,0 +1,18 @@ +--- +title: 자료 형 Data Types +actions: ['정답 확인하기', '힌트 보기'] +material: + editor: + language: sol + startingCode: | + answer: > +--- + +솔리디티 코드는 컨트렉트에 싸여 있지. 컨트렉트는 기본적으로... Solidity's code is encapsulated in contracts. A contract is basically... + +``` +contract HelloWorld + +``` + +# 직접 해보기 diff --git a/ko/index.json b/ko/index.json index 010428320..9dca417d2 100644 --- a/ko/index.json +++ b/ko/index.json @@ -91,9 +91,9 @@ "이 URL를 공유해서 친구들이 자네의 군대를 확인할 수 있도록 해보게:" ], "footer": [ - "레슨 4는 1-2주 후에 출시될 거네.", - "레슨이 준비되는 대로 이메일을 받을 수 있을 거네.", - "그동안 {telegramLink} 대화 방에 참가하거나 {twitterLink}에서 우리를 팔로우하게!" + "크립토좀비를 즐기고 있나? 우리에게 알려주게!", + "{telegramLink} 대화 방에 참가하거나 {twitterLink}에서 우리를 팔로우하게", + "준비가 되면 아래 버튼을 클릭하여 레슨 4을 진행하게:" ], "zombieDesc": "레벨 3 크립토좀비", "shareLinkText": "제가 방금 #CryptoZombies 레슨 3을 마쳤어요! 제 좀비 군대를 확인해 보세요:" diff --git a/no/index.json b/no/index.json index 3bf14dd41..5e29c3389 100644 --- a/no/index.json +++ b/no/index.json @@ -91,9 +91,9 @@ "Del denne URLen slik at vennene dine kan sjekke ut hæren din:" ], "footer": [ - "Leksjon 4 kommer om 1-2 uker", - "Du får en e-post fra oss så snart den er klar.", - "I mellomtiden bli med oss på {telegramlink}, eller følg vår {twitter Link} for å bli med i samtalen!" + "Nyter du CryptoZombies? Gi oss beskjed!", + "Bli med i samtalen på {telegramlink}, eller følg oss på {twitter Link}", + "Når du er klar, klikk på knappen nedenfor for å få tilgang til leksjon 4:" ], "zombieDesc": "En Level 3 CryptoZombie", "shareLinkText": "Jeg ble akkurat ferdig med #CryptoZombies Leksjon 3! Min zombie hær blir større!" diff --git a/pt/index.json b/pt/index.json index bbbd4a570..7aff8799a 100644 --- a/pt/index.json +++ b/pt/index.json @@ -91,9 +91,9 @@ "Compartilhe esta URL para que os seus vejam o seu exército:" ], "footer": [ - "Lição 4 estará pronta 1-2 semanas", - "Você receberá um email nosso assim que estiver pronto.", - "Enquanto isso, junte-se a nós {telegramLink}, ou siga-nos no {twitterLink} para juntar-se a conversa!" + "Gostou do CryptoZombies? Conte para nós!", + "Junte se a conversa no {telegramLink}, ou siga-nos no {twitterLink}", + "Quando você estiver pronto, clique no botão abaixo e acesse a Lição 4:" ], "zombieDesc": "Um CryptoZombie de Nível 3", "shareLinkText": "Eu acabei de completar a Lição 3 do #CryptoZombies! Olha só o meu exército de zumbi:" diff --git a/th/index.json b/th/index.json index 8abdc5286..2b0a4ba63 100644 --- a/th/index.json +++ b/th/index.json @@ -91,9 +91,9 @@ "แชร์ URL นี้เพื่อที่เพื่อนของคุณจะได้เข้ามาชมกองทัพซอมบี้ของคุณ:" ], "footer": [ - "บทเรียนที่ 4 จะมาภายในอีก 1-2 สัปดาห์", - "คุณจะได้รับ email จากเราเพื่อแจ้งเตือนเมื่อมีบทเรียนใหม่เข้ามา", - "ในระหว่างนี้อย่าลืมเข้ามาร่วมคุยกับเราที่ {telegramLink} หรือติดตามเราผ่านทาง {twitterLink}" + "รู้สึกอย่างไรกับ CryptoZombies? ได้โปรดบออกให้เราทราบ!", + "เข้ามาพูดคุยกับเราได้ที่ {telegramLink} หรือติดตามพวกเราทาง {twitterLink}", + "เมื่อคุณพร้อมแล้วให้คลิกที่ปุ่มด้านล่าง เพื่อเข้าสู่บทเรียนที่4:" ], "zombieDesc": "A Level 3 CryptoZombie", "shareLinkText": "เรียน #CryptoZombies บทที่ 3 จบแล้ว!! เข้ามาดูกองทัพซอมบี้ของฉันสิ!" diff --git a/zh/1/00-overview.md b/zh/1/00-overview.md index f8e38cb61..fc6d852a4 100644 --- a/zh/1/00-overview.md +++ b/zh/1/00-overview.md @@ -4,8 +4,8 @@ header: 人类,欢迎你 roadmap: roadmap.jpg --- -你认为你可以当一个合格的**CryptoZombie**, 嗯? +你认为你可以当一个合格的 **CryptoZombie**, 嗯? -这个教程会教你如何搭建一个以太网的游戏 +这个教程会教你如何搭建一个**以太网的游戏**。 -此课程为 Solidity 初学者设计,需要你对其他的程序语言有所了解(如 JavaScript). +此课程为 Solidity 初学者设计,需要你对其他的程序语言有所了解(如 JavaScript)。 diff --git a/zh/1/arrays.md b/zh/1/arrays.md index b09287110..06a920a48 100644 --- a/zh/1/arrays.md +++ b/zh/1/arrays.md @@ -39,7 +39,7 @@ material: } --- -如果你想建立一个集合,可以用 **_数组_**这样的数据类型. Solidity支持两种数组: **_静态_** 数组和**_动态_** 数组: +如果你想建立一个集合,可以用 **_数组_**这样的数据类型. Solidity 支持两种数组: **_静态_** 数组和**_动态_** 数组: ``` // 固定长度为2的静态数组: @@ -60,7 +60,7 @@ Person[] people; // dynamic Array, we can keep adding to it ## 公共数组 -你可以定义`public`数组, Solidity 会自动创建 **_getter_** 方法. 语法如下: +你可以定义 `public` 数组, Solidity 会自动创建 **_getter_** 方法. 语法如下: ``` Person[] public people; @@ -68,8 +68,8 @@ Person[] public people; 其它的合约可以从这个数组读取数据(但不能写入数据),所以这在合约中是一个有用的保存公共数据的模式。 -# 测试一把 +# 实战演习 为了把一个僵尸部队保存在我们的APP里,并且能够让其它APP看到这些僵尸,我们需要一个公共数组。 -1. 创建一个数据类型为`Zombie`的结构体数组,用`public`修饰,命名为:`zombies`. +1. 创建一个数据类型为 `Zombie` 的结构体数组,用 `public` 修饰,命名为:`zombies`. diff --git a/zh/1/arraysstructs2.md b/zh/1/arraysstructs2.md index 00f4e925b..e2c06a29a 100644 --- a/zh/1/arraysstructs2.md +++ b/zh/1/arraysstructs2.md @@ -47,7 +47,7 @@ material: } --- -### 创建新的结构体 +## 创建新的结构体 还记得上个例子中的 `Person` 结构吗? @@ -60,7 +60,7 @@ struct Person { Person[] public people; ``` -现在我们学习创建新的 `Person`结构,然后把它加入到名为`people` 的数组中. +现在我们学习创建新的 `Person` 结构,然后把它加入到名为 `people` 的数组中. ``` // 创建一个新的Person: @@ -86,9 +86,9 @@ numbers.push(15); // numbers is now equal to [5, 10, 15] ``` -# 测试一把 +# 实战演习 -让我们创建名为createZombie的函数来做点儿什么吧 +让我们创建名为createZombie的函数来做点儿什么吧。 -1. 在函数体里新创建一个`Zombie`, 然后把它加入 `zombies` 数组中. 新创建的僵尸的`name` 和 `dna`,来自于函数的参数 +1. 在函数体里新创建一个 `Zombie`, 然后把它加入 `zombies` 数组中。 新创建的僵尸的 `name` 和 `dna`,来自于函数的参数。 2. 让我们用一行代码简洁地完成它。 diff --git a/zh/1/contracts.md b/zh/1/contracts.md index 186b55263..73e652fab 100644 --- a/zh/1/contracts.md +++ b/zh/1/contracts.md @@ -19,9 +19,9 @@ material: 从最基本的开始入手: -Solidity的代码都包裹在 **合约**里面. 一份 `合约` 就是以太应币应用的基本模块, 所有的变量和函数都属于一份合约, 它是你所有应用的起点. +Solidity 的代码都包裹在**合约**里面. 一份`合约`就是以太应币应用的基本模块, 所有的变量和函数都属于一份合约, 它是你所有应用的起点. -一份名为`HelloWorld`的空合约如下: +一份名为 `HelloWorld` 的空合约如下: ``` contract HelloWorld { @@ -31,9 +31,9 @@ contract HelloWorld { ## 版本指令 -所有的solidity源码都必须冠以 "version pragma" — 标明 Solidity 编译器的版本. 以避免将来新的编译器可能破坏你的代码。 +所有的 Solidity 源码都必须冠以 "version pragma" — 标明 Solidity 编译器的版本. 以避免将来新的编译器可能破坏你的代码。 -例如: `pragma solidity ^0.4.19;` (当前 solidity 的最新版本是 0.4.19). +例如: `pragma solidity ^0.4.19;` (当前 Solidity 的最新版本是 0.4.19). 综上所述, 下面就是一个最基本的合约 — 每次建立一个新的项目时的第一段代码: @@ -45,12 +45,12 @@ contract HelloWorld { } ``` -# 测试一把 +# 实战演习 -为了建立我们的僵尸部队, 让我们先建立一个基础合约,称为 `ZombieFactory`. +为了建立我们的僵尸部队, 让我们先建立一个基础合约,称为 `ZombieFactory`。 -1. 在右边的输入框里输入`0.4.19`,我们的合约基于这个版本的编译器 +1. 在右边的输入框里输入 `0.4.19`,我们的合约基于这个版本的编译器。 -2. 建立一个空合约`ZombieFactory`. +2. 建立一个空合约 `ZombieFactory`。 -一切完毕,点击下面 "答案" . 如果没效果,点击 "提示". +一切完毕,点击下面 "答案" . 如果没效果,点击 "提示"。 diff --git a/zh/1/datatypes.md b/zh/1/datatypes.md index dcedd8a38..45b9cf679 100644 --- a/zh/1/datatypes.md +++ b/zh/1/datatypes.md @@ -25,7 +25,7 @@ material: 真棒!我们已经为我们的合约做了一个外壳, 下面学习 Solidity 中如何使用变量。 -**_状态变量_** 是被永久地保存在合约中。也就是说它们被写入以太币区块链中. 想象成写入一个数据库。 +**_状态变量_**是被永久地保存在合约中。也就是说它们被写入以太币区块链中. 想象成写入一个数据库。 ##### 例子: ``` @@ -35,16 +35,16 @@ contract Example { } ``` -在上面的例子中,定义`myUnsignedInteger`为`uint`类型,并赋值100。 +在上面的例子中,定义 `myUnsignedInteger` 为 `uint` 类型,并赋值100。 ## 无符号整数: `uint` -`uint` 无符号数据类型, 指 **其值不能是负数**,对于有符号的整数存在名为 `int`的数据类型 +`uint` 无符号数据类型, 指**其值不能是负数**,对于有符号的整数存在名为 `int` 的数据类型。 -> 注: Solidity中, `uint` 实际上是 `uint256`代名词, 一个256位的无符号整数。你也可以定义位数少的uints — `uint8`, `uint16`, `uint32`, 等.. 但一般来讲你愿意使用简单的`uint` 除非在某些特殊情况下,这我们后面会讲。 +> 注: Solidity中, `uint` 实际上是 `uint256`代名词, 一个256位的无符号整数。你也可以定义位数少的uints — `uint8`, `uint16`, `uint32`, 等…… 但一般来讲你愿意使用简单的 `uint`, 除非在某些特殊情况下,这我们后面会讲。 -# 测试一把 +# 实战演习 我们的僵尸DNA将由一个十六位数字组成。 -定义`dnaDigits`为`uint`数据类型, 并赋值 `16`。 +定义 `dnaDigits` 为 `uint` 数据类型, 并赋值 `16`。 diff --git a/zh/1/events.md b/zh/1/events.md index fa34daad8..d3d10f82d 100644 --- a/zh/1/events.md +++ b/zh/1/events.md @@ -75,7 +75,7 @@ material: 我们的合约几乎就要完成了!让我们加上一个**事件**. -**事件** 是合约和区块链通讯的一种机制。你的前端应用‘监听’某些事件,并做出反应。 +**事件** 是合约和区块链通讯的一种机制。你的前端应用“监听”某些事件,并做出反应。 例子: @@ -99,12 +99,12 @@ YourContract.IntegersAdded(function(error, result) { } ``` -# 测试一把 +# 实战演习 我们想每当一个僵尸创造出来时,我们的前端都能监听到这个事件,并将它显示出来。 -1. 定义一个 `事件` 叫做 `NewZombie`. 它有3个参数: `zombieId` (`uint`), `name` (`string`), 和 `dna` (`uint`). +1。 定义一个 `事件` 叫做 `NewZombie`。 它有3个参数: `zombieId` (`uint`), `name` (`string`), 和 `dna` (`uint`)。 -2. 修改 `_createZombie` 函数使得当新僵尸造出来并加入`zombies`数组后,生成事件`NewZombie`。 +2。 修改 `_createZombie` 函数使得当新僵尸造出来并加入`zombies`数组后,生成事件`NewZombie`。 -3. 需要定义僵尸`id`。 `array.push()` 返回数组的长度类型是`uint` - 因为数组的第一个元素的索引是 0, `array.push() - 1` 将是我们加入的僵尸的索引。 `zombies.push() - 1` 就是 `id`,数据类型是`uint`。在下一行中你可以把它用到`NewZombie` 事件中。 +3。 需要定义僵尸`id`。 `array。push()` 返回数组的长度类型是`uint` - 因为数组的第一个元素的索引是 0, `array。push() - 1` 将是我们加入的僵尸的索引。 `zombies。push() - 1` 就是 `id`,数据类型是`uint`。在下一行中你可以把它用到`NewZombie` 事件中。 diff --git a/zh/1/functions.md b/zh/1/functions.md index 45dbf5f1d..814692504 100644 --- a/zh/1/functions.md +++ b/zh/1/functions.md @@ -63,7 +63,7 @@ function eatHamburgers(string _name, uint _amount) { eatHamburgers("vitalik", 100); ``` -# 测试一把 +# 实战演习 在我们的应用里,我们要能创建一些僵尸,让我们写一个函数做这件事吧! diff --git a/zh/1/functions2.md b/zh/1/functions2.md index 49370bdb3..09abb6de0 100644 --- a/zh/1/functions2.md +++ b/zh/1/functions2.md @@ -63,10 +63,10 @@ function _addToArray(uint _number) private { 这意味着只有我们合约中的其它函数才能够调用这个函数,给 `numbers` 数组添加新成员。 -可以看到,在函数名字后面使用关键字 `private` 即可。和函数的参数类似, 私有函数的名字用(`_`)起始. +可以看到,在函数名字后面使用关键字 `private` 即可。和函数的参数类似,私有函数的名字用(`_`)起始。 -# 测试一把 +# 实战演习 -我们合约的函数`createZombie` 的默认属性是公共的,这意味着任何一方都可以调用它去创建一个僵尸。 咱们来把它变成私有吧! +我们合约的函数 `createZombie` 的默认属性是公共的,这意味着任何一方都可以调用它去创建一个僵尸。 咱们来把它变成私有吧! 1. 变 `createZombie` 为私有函数,不要忘记遵守命名的规矩哦! diff --git a/zh/1/functions3.md b/zh/1/functions3.md index 2dc8ca303..59b99bb46 100644 --- a/zh/1/functions3.md +++ b/zh/1/functions3.md @@ -79,7 +79,7 @@ Solidity 里,函数的定义里可包含返回值的数据类型(如本例中 function sayHello() public view returns (string) { ``` -Solidity 还支持 **_pure_** 函数, 表明这个函数甚至都不能访问程序里的数据,例如: +Solidity 还支持 **_pure_** 函数, 表明这个函数甚至都不访问应用里的数据,例如: ``` function _multiply(uint a, uint b) private pure returns (uint) { @@ -87,15 +87,15 @@ function _multiply(uint a, uint b) private pure returns (uint) { } ``` -这个函数甚至都不能读取程序里的状态 — 它的返回值完全取决于它的输入参数,在这种情况下我们把函数定义为 **_pure_**. +这个函数甚至都不读取应用里的状态 — 它的返回值完全取决于它的输入参数,在这种情况下我们把函数定义为 **_pure_**. > 注:可能很难记住何时把函数标记为 pure/view。 幸运的是, Solidity 编辑器会给出提示,提醒你使用这些修饰符。 -# 测试一把 +# 实战演习 我们想建立一个帮助函数,它根据一个字符串随机生成一个DNA数据。 -1. 创建一个`private` 函数,命名为 `_generateRandomDna`。它只接收一个输入变量`_str` (类型`string`), 返回一个`uint`类型的数值。 +1. 创建一个 `private` 函数,命名为 `_generateRandomDna`。它只接收一个输入变量 `_str` (类型 `string`), 返回一个 `uint` 类型的数值。 2. 此函数只读取我们合约中的一些变量,所以标记为`view`。 diff --git a/zh/1/keccak256.md b/zh/1/keccak256.md index 9fffb29d2..2600cd6f6 100644 --- a/zh/1/keccak256.md +++ b/zh/1/keccak256.md @@ -88,12 +88,12 @@ uint8 c = a * b; uint8 c = a * uint8(b); ``` -上面, `a * b` 返回类型是 `uint`, 但是当我们尝试用`uint8`类型接收时, 就会造成潜在的错误。如果把它的数据类型转换为`uint8`, 就可以了,编译器也不会出错。 +上面, `a * b` 返回类型是 `uint`, 但是当我们尝试用 `uint8` 类型接收时, 就会造成潜在的错误。如果把它的数据类型转换为 `uint8`, 就可以了,编译器也不会出错。 -# 测试一把 +# 实战演习 -给`_generateRandomDna` 函数添加代码! 它应该完成如下功能: +给 `_generateRandomDna` 函数添加代码! 它应该完成如下功能: -1. 第一行代码取`_str`的`keccak256`散列值,伪随机十六进制数。类型转换为`uint`, 最后保存在类型为 `uint` 名为 `rand`的变量中。 +1. 第一行代码取 `_str` 的 `keccak256` 散列值生成一个伪随机十六进制数,类型转换为 `uint`, 最后保存在类型为 `uint` 名为 `rand` 的变量中。 -2. 我们只想让我们的DNA的长度为16位 (还记得 `dnaModulus`?)。所以第二行代码应该`return` 上面计算的数值对 `dnaModulus`求余数(`%`). +2. 我们只想让我们的DNA的长度为16位 (还记得 `dnaModulus`?)。所以第二行代码应该 `return` 上面计算的数值对 `dnaModulus` 求余数(`%`)。 diff --git a/zh/1/lessonoverview.md b/zh/1/lessonoverview.md index 57fbb28c5..753c9acb6 100644 --- a/zh/1/lessonoverview.md +++ b/zh/1/lessonoverview.md @@ -34,8 +34,8 @@ material: 在右边页面,移动头基因`head gene` 滑块到第七位置(圣诞帽)可见`83`所对应的特点。 -# 测试一把 +# 实战演习 1. 玩一下页面右侧的滑块。检验一下不同的数字对应不同的僵尸的长相。 -好了,这已经足够你玩一会儿了。 当你想继续的时候,点击下面的"下一章" ,让我们来钻研 Solidity! +好了,这已经足够你玩一会儿了。 当你想继续的时候,点击下面的"下一章",让我们来钻研 Solidity ! diff --git a/zh/1/math.md b/zh/1/math.md index 915d8b69a..be90b5dfc 100644 --- a/zh/1/math.md +++ b/zh/1/math.md @@ -36,8 +36,10 @@ material: Solidity 还支持 **_乘方操作_** (如:x 的 y次方) // 例如: 5 ** 2 = 25 ``` +uint x = 5 ** 2; // equal to 5^2 = 25 +``` -# 测试一把 +# 实战演习 为了保证我们的僵尸的DNA只含有16个字符,我们先造一个`uint`数据,让它等于10^16。这样一来以后我们可以用模运算符 `%` 把一个整数变成16位。 diff --git a/zh/1/puttingittogether.md b/zh/1/puttingittogether.md index 3bd3151c9..a48619c11 100644 --- a/zh/1/puttingittogether.md +++ b/zh/1/puttingittogether.md @@ -68,12 +68,12 @@ material: 写一个公共函数,它有一个参数,用来接收僵尸的名字,之后用它生成僵尸的DNA。 -# 测试一把 +# 实战演习 -1. 创建一个 `public` 函数,命名为` createRandomZombie`. 它将被传入一个变量 `_name` (数据类型是 `string`). _(注: 定义公共函数 `public` 和定义一个私有 `private` 函数的做法一样)_ +1. 创建一个 `public` 函数,命名为` createRandomZombie`. 它将被传入一个变量 `_name` (数据类型是 `string`)。 _(注: 定义公共函数 `public` 和定义一个私有 `private` 函数的做法一样)_。 -2. 函数的第一行应该调用 `_generateRandomDna` 函数,传入 `_name` 参数, 结果保存在一个类型为 `uint` 的变量里,命名为 `randDna`. +2. 函数的第一行应该调用 `_generateRandomDna` 函数,传入 `_name` 参数, 结果保存在一个类型为 `uint` 的变量里,命名为 `randDna`。 -3. 第二行调用 `_createZombie` 函数, 传入参数: `_name` 和 `randDna`. +3. 第二行调用 `_createZombie` 函数, 传入参数: `_name` 和 `randDna`。 4. 整个函数应该是4行代码 (包括函数的结束符号 `}` )。 diff --git a/zh/1/structs.md b/zh/1/structs.md index ec3d73d29..3f2013964 100644 --- a/zh/1/structs.md +++ b/zh/1/structs.md @@ -44,9 +44,9 @@ struct Person { 结构体允许你生成一个更复杂的数据类型,它有多个属性。 -> 注:我们刚刚引进了一个新类型, `string`。 字符串用于保存任意长度的 UTF-8 编码数据。 如: `string greeting = "Hello world!"` +> 注:我们刚刚引进了一个新类型, `string`。 字符串用于保存任意长度的 UTF-8 编码数据。 如: `string greeting = "Hello world!"`。 -# 测试一把 +# 实战演习 在我们的程序中,我们将创建一些僵尸!每个僵尸将拥有多个属性,所以这是一个展示结构体的完美例子。 diff --git a/zh/1/web3js.md b/zh/1/web3js.md index d18520686..0e743558c 100644 --- a/zh/1/web3js.md +++ b/zh/1/web3js.md @@ -12,9 +12,9 @@ material: 我们的 Solidity 合约完工了! 现在我们要写一段 JavaScript 前端代码来调用这个合约。 -以太坊有一个 JavaScript 库,名为**_Web3.js_**. +以太坊有一个 JavaScript 库,名为**_Web3.js_**。 -在后面的课程里,我们会进一步地教你如何安装一个合约,如何设置Web3.js. 但是现在我们通过一段代码来了解 Web3.js 是如何和我们发布的合约交互的吧。 +在后面的课程里,我们会进一步地教你如何安装一个合约,如何设置Web3.js。 但是现在我们通过一段代码来了解 Web3.js 是如何和我们发布的合约交互的吧。 如果下面的代码你不能全都理解,不用担心。 ``` diff --git a/zh/2/00-overview.md b/zh/2/00-overview.md index 8318c5c25..0065ddd53 100644 --- a/zh/2/00-overview.md +++ b/zh/2/00-overview.md @@ -1,9 +1,9 @@ --- title: 僵尸攻击人类 -header: 所以,你需要晋级至第二课! +header: 你成功晋升到第二课啦! roadmap: roadmap2.jpg --- 厉害了,我的人类! 你比我设想的更会编程! -第二课中,你会学到如何通过吞噬其他生物,扩张你的僵尸军团 -在这一课里,我们会使用到一些高级的Solidity概念,所以你一定要先完成第一课。 +第二课中,你会学到如何通过猎食其他生物,扩张你的僵尸军团 +在这一课里,我们会使用到一些高级的 Solidity 概念,所以你一定要先完成第一课。 diff --git a/zh/2/1-overview.md b/zh/2/1-overview.md index c71fe2fbf..d60df045a 100644 --- a/zh/2/1-overview.md +++ b/zh/2/1-overview.md @@ -1,5 +1,5 @@ --- -title: Lesson 2 Overview +title: 第二课概览 actions: ['checkAnswer', 'hints'] material: saveZombie: false @@ -12,13 +12,13 @@ material: --- 在第一课中,我们创建了一个命名函数用来生成僵尸,并且将它放入区块链上的僵尸数据库中。 -在第二课里,我们会让我们的app看起来更像一个游戏: 它得支持多用户,并且采用更加有趣--而不仅仅随机--的方式,来生成新的僵尸。 +在第二课里,我们会让我们的 app 看起来更像一个游戏: 它得支持多用户,并且采用更加有趣--而不仅仅随机--的方式,来生成新的僵尸。 如何生成新的僵尸呢?通过让现有的僵尸猎食其他生物! ## 僵尸猎食 -僵尸猎食的时候, 僵尸病毒侵入猎物, 这些病毒会将猎物变为新的僵尸,加入你的Solidity。系统会通过猎物和猎食者僵尸的DNA计算出新僵尸的DNA。 +僵尸猎食的时候,僵尸病毒侵入猎物,这些病毒会将猎物变为新的僵尸,加入你的僵尸大军。系统会通过猎物和猎食者僵尸的DNA计算出新僵尸的DNA。 僵尸最喜欢猎食什么物种呢? 等你学完第二课就知道了! diff --git a/zh/2/10-interactingcontracts.md b/zh/2/10-interactingcontracts.md index 3a4656d01..eba5e116c 100644 --- a/zh/2/10-interactingcontracts.md +++ b/zh/2/10-interactingcontracts.md @@ -1,5 +1,5 @@ --- -title: What Do Zombies Eat? +title: 僵尸吃什么? actions: ['checkAnswer', 'hints'] material: editor: @@ -97,15 +97,15 @@ material: 是时候让我们的僵尸去捕猎! 那僵尸最喜欢的食物是什么呢? -Crypto僵尸喜欢吃的是... +Crypto 僵尸喜欢吃的是... ** CryptoKitties!** 😱😱😱 (正经点,我可不是开玩笑😆) -为了做到这一点,我们要读出CryptoKitties智能合约中的kittyDna。这些数据是公开存储在区块链上的。区块链是不是很酷? +为了做到这一点,我们要读出 CryptoKitties 智能合约中的 kittyDna。这些数据是公开存储在区块链上的。区块链是不是很酷? -别担心 - 我们的游戏并不会伤害到任何真正的CryptoKitty。 我们只读取* CryptoKitties*数据,但却无法物理上删除它。 +别担心 —— 我们的游戏并不会伤害到任何真正的CryptoKitty。 我们只 *读取* CryptoKitties 数据,但却无法物理上删除它。 ##与其他合约的交互 @@ -130,9 +130,9 @@ contract LuckyNumber { 这是个很简单的合约,您可以用它存储自己的幸运号码,并将其与您的以太坊地址关联。 这样其他人就可以通过您的地址查找您的幸运号码了。 -现在假设我们有一个外部合约,使用`getNum`函数可读取其中的数据。 +现在假设我们有一个外部合约,使用 `getNum` 函数可读取其中的数据。 -首先,我们定义“LuckyNumber”合约的** _interface_ **: +首先,我们定义 `LuckyNumber` 合约的 ** _interface_ **: ``` @@ -143,19 +143,19 @@ contract NumberInterface { 请注意,这个过程虽然看起来像在定义一个合约,但其实内里不同: -首先,我们只声明了要与之交互的函数 - 在本例中为`getNum` - 在其中我们没有使用到任何关于函数或状态的变量。 +首先,我们只声明了要与之交互的函数 —— 在本例中为 `getNum` —— 在其中我们没有使用到任何关于函数或状态变量。 -其次,我们并没有使用大括号(`{`和`}`)定义函数体,我们单单用分号(`;`)结束了函数声明。这使它看起来像一个合约框架。 +其次,我们并没有使用大括号(`{` 和 `}`)定义函数体,我们单单用分号(`;`)结束了函数声明。这使它看起来像一个合约框架。 编译器就是靠这些特征认出它是一个接口的。 -在我们的app代码中使用这个接口,合约就知道其他合约的函数是怎样的,应该如何调用,以及可期待什么类型的返回。 +在我们的 app 代码中使用这个接口,合约就知道其他合约的函数是怎样的,应该如何调用,以及可期待什么类型的返回。 -在下一课中,我们将真正调用其他合约的函数。目前我们只要声明一个接口,用于调用CryptoKitties合约就行了。 +在下一课中,我们将真正调用其他合约的函数。目前我们只要声明一个接口,用于调用 CryptoKitties 合约就行了。 # 实战演习 -我们已经为你查看过了CryptoKitties的源代码,并且找到了一个名为`getKitty`的函数,它返回所有的加密猫的数据,包括它的“基因”(我们的僵尸游戏要用它生成新的僵尸)。 +我们已经为你查看过了 CryptoKitties 的源代码,并且找到了一个名为 `getKitty `的函数,它返回所有的加密猫的数据,包括它的“基因”(我们的僵尸游戏要用它生成新的僵尸)。 该函数如下所示: @@ -188,10 +188,10 @@ function getKitty(uint256 _id) external view returns ( } ``` -这个函数看起来跟我们习惯的函数不太一样。 它竟然返回了...一堆不同的值! 如果您用过Javascript之类的编程语言,一定会感到奇怪 - 在Solidity中,您可以让一个函数返回多个值。 +这个函数看起来跟我们习惯的函数不太一样。 它竟然返回了...一堆不同的值! 如果您用过 JavaScript 之类的编程语言,一定会感到奇怪 —— 在 Solidity中,您可以让一个函数返回多个值。 现在我们知道这个函数长什么样的了,就可以用它来创建一个接口: -1.定义一个名为`KittyInterface`的接口。 请注意,因为我们使用了`contract`关键字, 这过程看起来就像创建一个新的合同一样。 +1.定义一个名为 `KittyInterface` 的接口。 请注意,因为我们使用了 `contract` 关键字, 这过程看起来就像创建一个新的合约一样。 -2.在interface里定义了`getKitty`函数(不过是复制/粘贴上面的函数,但在`returns`语句之后用分号,而不是大括号内的所有内容。 +2.在interface里定义了 `getKitty` 函数(不过是复制/粘贴上面的函数,但在 `returns` 语句之后用分号,而不是大括号内的所有内容。 diff --git a/zh/2/11-interactingcontracts2.md b/zh/2/11-interactingcontracts2.md index a5b022eb1..559b8048d 100644 --- a/zh/2/11-interactingcontracts2.md +++ b/zh/2/11-interactingcontracts2.md @@ -1,5 +1,5 @@ --- -title: Using an Interface +title: 使用接口 actions: ['checkAnswer', 'hints'] material: editor: @@ -114,7 +114,7 @@ material: } --- -继续前面`NumberInterface`的例子,我们既然将接口定义为: +继续前面 `NumberInterface` 的例子,我们既然将接口定义为: ``` contract NumberInterface { @@ -141,8 +141,8 @@ contract MyContract { 通过这种方式,只要将您合约的可见性设置为“公共”或“外部”,它们就可以与以太坊区块链上的任何其他合同进行交互。 -#小测验 +# 实战演习 -我们来建个自己的合约去读取另一个智能合约--CryptoKitties的内容吧! +我们来建个自己的合约去读取另一个智能合约-- CryptoKitties 的内容吧! -1.我已经将代码中CryptoKitties合约的地址保存在一个名为`ckAddress`的变量中。在下一行中,请创建一个名为`kittyContract`的KittyInterface,并用`ckAddress`为它初始化 - 就像我们为`numberContract`所做的一样。 +1. 我已经将代码中 CryptoKitties 合约的地址保存在一个名为 `ckAddress` 的变量中。在下一行中,请创建一个名为 `kittyContract` 的 KittyInterface,并用 `ckAddress` 为它初始化 —— 就像我们为 `numberContract`所做的一样。 diff --git a/zh/2/12-multiplereturns.md b/zh/2/12-multiplereturns.md index 94e1ac9de..c63f5af00 100644 --- a/zh/2/12-multiplereturns.md +++ b/zh/2/12-multiplereturns.md @@ -1,5 +1,5 @@ --- -title: Handling Multiple Return Values +title: 处理多返回值 actions: ['checkAnswer', 'hints'] material: editor: @@ -122,7 +122,7 @@ material: } --- -`getKitty`是我们所看到的第一个返回多个值的函数。我们来看看是如何处理的: +`getKitty` 是我们所看到的第一个返回多个值的函数。我们来看看是如何处理的: ``` function multipleReturns() internal returns(uint a, uint b, uint c) { @@ -133,7 +133,7 @@ function processMultipleReturns() external { uint a; uint b; uint c; - // This is how you do multiple assignment: + // 这样来做批量赋值: (a, b, c) = multipleReturns(); } @@ -147,16 +147,16 @@ function getLastReturnValue() external { # 实战演习 -是时候与CryptoKitties合约交互起来了! +是时候与 CryptoKitties 合约交互起来了! -我们来定义一个函数,从kitty 合约中获取它的基因: +我们来定义一个函数,从 kitty 合约中获取它的基因: -1.创建一个名为`feedOnKitty`的函数。它需要2个`uint`类型的参数,`_zombieId`和`_kittyId`,这是一个`public`类型的函数。 +1. 创建一个名为 `feedOnKitty` 的函数。它需要2个 `uint` 类型的参数,`_zombieId` 和`_kittyId` ,这是一个 `public` 类型的函数。 -2.函数首先要声明一个名为`kittyDna`的`uint`。 +2. 函数首先要声明一个名为 `kittyDna` 的 `uint`。 -  >注意:在我们的KittyInterface中,`genes`是一个`uint256`类型的变量,但是如果你记得,我们在第一课中提到过,`uint`是`uint256`的别名,也就是说它们是一回事。 + >注意:在我们的 `KittyInterface` 中,`genes` 是一个 `uint256` 类型的变量,但是如果你记得,我们在第一课中提到过,`uint` 是 `uint256` 的别名,也就是说它们是一回事。 -3.这个函数接下来使用了 _kittyId 参数,去调用`kittyContract.getKitty`函数,并将返回的 `genes`存储在`kittyDna`中。记住 - `getKitty`会返回一大堆变量。 (确切地说10个 - 我已经为你数过了,不错吧!)。但是我们只关心最后一个--“基因”。数逗号的时候小心点哦! +3. 这个函数接下来调用 `kittyContract.getKitty`函数, 传入 `_kittyId` ,将返回的 `genes` 存储在 `kittyDna` 中。记住 —— `getKitty` 会返回一大堆变量。 (确切地说10个 - 我已经为你数过了,不错吧!)。但是我们只关心最后一个-- `genes`。数逗号的时候小心点哦! -4.最后,函数调用了`feedAndMultiply`,并传入了`_zombieId`和`kittyDna`两个参数。 +4. 最后,函数调用了 `feedAndMultiply` ,并传入了 `_zombieId` 和 `kittyDna` 两个参数。 diff --git a/zh/2/13-kittygenes.md b/zh/2/13-kittygenes.md index b3ece5195..7d0b6b74a 100644 --- a/zh/2/13-kittygenes.md +++ b/zh/2/13-kittygenes.md @@ -1,5 +1,5 @@ --- -title: "Bonus: Kitty Genes" +title: "奖励: Kitty 基因" actions: ['checkAnswer', 'hints'] material: editor: @@ -135,18 +135,18 @@ material: 我们的功能逻辑主体已经完成了...现在让我们来添一个奖励功能吧。 -这样吧,给从小猫制造出的僵尸有添加些特征,以显示他们是猫僵尸。 +这样吧,给从小猫制造出的僵尸添加些特征,以显示他们是猫僵尸。 要做到这一点,咱们在新僵尸的DNA中添加一些特殊的小猫代码。 还记得吗,第一课中我们提到,我们目前只使用16位DNA的前12位数来指定僵尸的外观。所以现在我们可以使用最后2个数字来处理“特殊”的特征。 -这样吧,把猫僵尸DNA的最后两个数字设定为“99”(因为猫有9条命)。所以在我们这么来写代码:`如果`这个僵尸是一只猫变来的,就将它DNA的最后两位数字设置为`99`。 +这样吧,把猫僵尸DNA的最后两个数字设定为`99`(因为猫有9条命)。所以在我们这么来写代码:`如果`这个僵尸是一只猫变来的,就将它DNA的最后两位数字设置为`99`。 ## if 语句 -if语句的语法在 Solidity中,与在javascript差不多: +if语句的语法在 Solidity 中,与在 JavaScript 中差不多: ``` function eatBLT(string sandwich) public { // 看清楚了,当我们比较字符串的时候,需要比较他们的 keccak256 哈希码 @@ -160,14 +160,12 @@ function eatBLT(string sandwich) public { 让我们在我们的僵尸代码中实现小猫的基因。 -1.首先,我们修改下`feedAndMultiply`函数的定义,给它传入第三个参数:一条名为`species`的字符串 +1. 首先,我们修改下 `feedAndMultiply` 函数的定义,给它传入第三个参数:一条名为 `_species` 的字符串。 -2.接下来,在我们计算出新的僵尸的DNA之后,添加一个`if`语句来比较`species`和字符串``kitty``的keccak256`哈希值 +2. 接下来,在我们计算出新的僵尸的DNA之后,添加一个 `if` 语句来比较 `_species` 和字符串 `"kitty"` 的 `keccak256` 哈希值。 -在`if`语句中,我们想用`99`替换DNA的最后两位数字。一种方法是使用逻辑:`newDna = newDna - newDna%100 + 99;`。 +3. 在 `if` 语句中,我们用 `99` 替换了新僵尸DNA的最后两位数字。可以这么做:`newDna = newDna - newDna%100 + 99;`。 -3. 在`if`语句中,我们用`99`替换了新僵尸DNA的最后两位数字。可以这么做:`newDna = newDna - newDna%100 + 99;`。 + >解释:假设 `newDna` 是 `334455`。那么 `newDna%100` 是 `55`,所以 `newDna - newDna%100` 得到 `334400`。最后加上 `99` 可得到 `334499`。 - >解释:假设`newDna`是`334455`。那么`newDna%100`是`55`,所以`newDna - newDna%100`得到`334400`。最后加上`99`可得到`334499`。 - -4.最后,我们修改了`feedOnKitty`中的函数调用。当它调用`feedAndMultiply`时,增加了`“kitty”`作为最后一个参数。 +4. 最后,我们修改了 `feedOnKitty` 中的函数调用。当它调用 `feedAndMultiply` 时,增加 `“kitty”` 作为最后一个参数。 diff --git a/zh/2/14-wrappingitup.md b/zh/2/14-wrappingitup.md index 1056f3297..06519ef19 100644 --- a/zh/2/14-wrappingitup.md +++ b/zh/2/14-wrappingitup.md @@ -1,5 +1,5 @@ --- -title: Wrapping It Up +title: 放在一起 actions: ['checkAnswer', 'hints'] material: saveZombie: true @@ -15,11 +15,11 @@ material: 查看下→_→的演示,看看他们怎么运行起来得吧。继续,你肯定等不及看完这一页😉。点击小猫,攻击!看到你斩获一个新的小猫僵尸了吧! -## Javascript 实现 +## JavaScript 实现 -我们只用编译和部署`ZombieFeeding`,就可以将这个合约部署到以太坊了。 - 我们最终完成的这个合约继承自`ZombieFactory`,因此它可以访问自己和父辈合约中的所有public方法。 +我们只用编译和部署 `ZombieFeeding`,就可以将这个合约部署到以太坊了。我们最终完成的这个合约继承自 `ZombieFactory`,因此它可以访问自己和父辈合约中的所有 public 方法。 -我们来看一个与我们的刚部署的合约进行交互的例子, 这个例子使用了Javascript和web3.js: +我们来看一个与我们的刚部署的合约进行交互的例子, 这个例子使用了 JavaScript 和 web3.js: ``` var abi = /* abi generated by the compiler */ @@ -27,37 +27,36 @@ var ZombieFeedingContract = web3.eth.contract(abi) var contractAddress = /* our contract address on Ethereum after deploying */ var ZombieFeeding = ZombieFeedingContract.at(contractAddress) -// Assuming we have our zombie's ID and the kitty ID we want to attack +// 假设我们有我们的僵尸ID和要攻击的猫咪ID let zombieId = 1; let kittyId = 1; -// To get the CryptoKitty's image, we need to query their web API. This -// information isn't stored on the blockchain, just their webserver. -// If everything was stored on a blockchain, we wouldn't have to worry -// about the server going down, them changing their API, or the company -// blocking us from loading their assets if they don't like our zombie game ;) +// 要拿到猫咪的DNA,我们需要调用它的API。这些数据保存在它们的服务器上而不是区块链上。 +// 如果一切都在区块链上,我们就不用担心它们的服务器挂了,或者它们修改了API, +// 或者因为不喜欢我们的僵尸游戏而封杀了我们 let apiUrl = "https://api.cryptokitties.co/kitties/" + kittyId $.get(apiUrl, function(data) { let imgUrl = data.image_url - // do something to display the image + // 一些显示图片的代码 }) -// When the user clicks on a kitty: +// 当用户点击一只猫咪的时候: $(".kittyImage").click(function(e) { - // Call our contract's `feedOnKitty` method + // 调用我们合约的 `feedOnKitty` 函数 ZombieFeeding.feedOnKitty(zombieId, kittyId) }) -// Listen for a NewZombie event from our contract so we can display it: +// 侦听来自我们合约的新僵尸事件好来处理 ZombieFactory.NewZombie(function(error, result) { if (error) return - // This function will display the zombie, like in lesson 1: + // 这个函数用来显示僵尸: generateZombie(result.zombieId, result.name, result.dna) }) ``` +# 实战演习 -选择一只你想猎食的小猫。你自家僵尸的DNA会和小猫的DNA结合,生成一个新的小猫僵尸,加入你的军团! +选择一只你想猎食的小猫。你自家僵尸的 DNA 会和小猫的 DNA 结合,生成一个新的小猫僵尸,加入你的军团! -看到新僵尸上那可爱的猫咪腿了么?这是新僵尸最后DNA中最后两位数字“99”的功劳! +看到新僵尸上那可爱的猫咪腿了么?这是新僵尸最后DNA中最后两位数字 `99` 的功劳! 你想要的话随时可以重新开始。捕获了一只猫咪僵尸,你一定很高兴吧!(不过你只能持有一只),继续前进到下一章,完成第二课吧! diff --git a/zh/2/15-lessoncomplete.md b/zh/2/15-lessoncomplete.md index 6954aa4d2..faf902357 100644 --- a/zh/2/15-lessoncomplete.md +++ b/zh/2/15-lessoncomplete.md @@ -1,5 +1,5 @@ --- -title: Lesson 2 Complete! +title: 第二课完成了! actions: ['checkAnswer', 'hints'] material: lessonComplete: diff --git a/zh/2/2-mappings.md b/zh/2/2-mappings.md index f5bed695f..a1171d0ae 100644 --- a/zh/2/2-mappings.md +++ b/zh/2/2-mappings.md @@ -1,5 +1,5 @@ --- -title: Mappings and Addresses +title: 映射(Mapping)和地址(Address) actions: ['checkAnswer', 'hints'] material: editor: @@ -21,7 +21,7 @@ material: Zombie[] public zombies; - // declare mappings here + // 在这里定义映射 function _createZombie(string _name, uint _dna) private { uint id = zombies.push(Zombie(_name, _dna)) - 1; @@ -80,43 +80,43 @@ material: 我们通过给数据库中的僵尸指定“主人”, 来支持“多玩家”模式。 -如此一来,我们需要引入2个新的数据类型:“映射”和“地址”。 +如此一来,我们需要引入2个新的数据类型:`mapping`(映射) 和 `address`(地址)。 -## 地址 +## Addresses (地址) -以太坊区块链由** _ account _ **(账户号)组成,您可以把它想象成银行账户。一个帐户的余额是** _以太_ **(在以太坊区块链上使用的币种),您可以和其他帐户之间支付和接受以太币,就像您的银行帐户可以电汇资金到其他银行帐户一样。 +以太坊区块链由** _ account _ **(账户)组成,你可以把它想象成银行账户。一个帐户的余额是** _以太_ **(在以太坊区块链上使用的币种),你可以和其他帐户之间支付和接受以太币,就像你的银行帐户可以电汇资金到其他银行帐户一样。 -每个帐户都有一个“地址”,您可以把它想象成银行账号。这是账户唯一的标识符,它看起来长这样: +每个帐户都有一个“地址”,你可以把它想象成银行账号。这是账户唯一的标识符,它看起来长这样: `0x0cE446255506E92DF41614C46F1d6df9Cc969183` -(这是CryptoZombies团队的地址,如果你喜欢CryptoZombies的话,请打赏我们一些以太币!😉) +(这是 CryptoZombies 团队的地址,如果你喜欢 CryptoZombies 的话,请打赏我们一些以太币!😉) -我们将在后面的课程中介绍地址的细节,现在您只需要了解**地址属于特定用户(或智能合约)的**。 +我们将在后面的课程中介绍地址的细节,现在你只需要了解**地址属于特定用户(或智能合约)的**。 -所以我们可以指定“地址”作为僵尸主人的ID。当用户通过与我们的应用程序交互来创建新的僵尸时,新僵尸的所有权被设置到调用者的太坊地址名下。 +所以我们可以指定“地址”作为僵尸主人的 ID。当用户通过与我们的应用程序交互来创建新的僵尸时,新僵尸的所有权被设置到调用者的太坊地址名下。 -##映射 +## Mapping(映射) -在第1课中,我们看了** _ structs _ ** 和 ** _ arrays _ **。 ** _映射_ **是另一种在Solidity中存储有组织数据的方法。 +在第1课中,我们看了** _ structs _ ** 和 ** _ arrays _ **。 ** _映射_ **是另一种在 Solidity 中存储有组织数据的方法。 映射是这样定义的: ``` -//对于金融应用程序,用以保存用户账户余额的单元: +//对于金融应用程序,将用户的余额保存为一个 uint: mapping (address => uint) public accountBalance; //或者可以用来通过userId 存储/查找的用户名 mapping (uint => string) userIdToName; ``` -映射本质上是存储和查找数据所用的键-值对。在第一个例子中,键是一个“地址”,值是一个“单元”,在第二个例子中,键是一个“单元”,值是一个“字符串”。 +映射本质上是存储和查找数据所用的键-值对。在第一个例子中,键是一个 `address`,值是一个 `uint`,在第二个例子中,键是一个`uint`,值是一个 `string`。 # 实战演习 -为了存储僵尸的所有权,我们会使用到两个映射:一个记录僵尸拥有者的地址,另一个记录某ID所拥有僵尸的数量。 +为了存储僵尸的所有权,我们会使用到两个映射:一个记录僵尸拥有者的地址,另一个记录某 ID 所拥有僵尸的数量。 -1.创建一个叫做“zombieToOwner”的映射。将键作为“单元”(我们将根据它的id存储和查找僵尸),值为“address”。映射属性为`public`。 +1.创建一个叫做 `zombieToOwner` 的映射。其键是一个`uint`(我们将根据它的 id 存储和查找僵尸),值为 `adress`。映射属性为`public`。 -2.创建一个名为`ownerZombieCount`的映射,其中键是一个“地址”,值是`单元(unit)`。 +2.创建一个名为 `ownerZombieCount` 的映射,其中键是一个 `address`,值是 `uint`。 diff --git a/zh/2/3-msgsender.md b/zh/2/3-msgsender.md index e46048814..cd1e87482 100644 --- a/zh/2/3-msgsender.md +++ b/zh/2/3-msgsender.md @@ -26,7 +26,7 @@ material: function _createZombie(string _name, uint _dna) private { uint id = zombies.push(Zombie(_name, _dna)) - 1; - // start here + // 从这里开始 NewZombie(id, _name, _dna); } @@ -82,47 +82,47 @@ material: } --- -现在有了一套映射来记录僵尸的所有权了,我们可以修改`_createZombie`方法来运用它们。 +现在有了一套映射来记录僵尸的所有权了,我们可以修改 `_createZombie` 方法来运用它们。 -为了做到这一点,我们要用到“msg.sender”。 +为了做到这一点,我们要用到 `msg.sender`。 ## msg.sender -在Solidity中,有一些全局变量可以被所有功能调用。 其中一个就是“msg.sender”,它指的是当前调用者(或智能合约)的“地址”。 +在 Solidity 中,有一些全局变量可以被所有功能调用。 其中一个就是 `msg.sender`,它指的是当前调用者(或智能合约)的 `address`。 ->注意:在Solidity中,功能执行始终需要从外部调用者开始。 一个合约只会在区块链上什么也做不了,除非有调用者呼叫其中的函数。 调用者就是`msg.sender`。 +>注意:在 Solidity 中,功能执行始终需要从外部调用者开始。 一个合约只会在区块链上什么也不做,除非有人调用其中的函数。调用者就是 `msg.sender`。 -以下是使用`msg.sender`来更新`mapping`的例子: +以下是使用 `msg.sender` 来更新 `mapping` 的例子: ``` mapping (address => uint) favoriteNumber; function setMyNumber(uint _myNumber) public { - // Update our `favoriteNumber` mapping to store `_myNumber` under `msg.sender` + // 更新我们的 `favoriteNumber` 映射来将 `_myNumber` 存储给 `msg.sender` favoriteNumber[msg.sender] = _myNumber; - // ^ The syntax for storing data in a mapping is just like with arrays + // ^ 存储数据至映射的方法和数组一样 } function whatIsMyNumber() public view returns (uint) { - // Retrieve the value stored in the sender's address - // Will be `0` if the sender hasn't called `setMyNumber` yet + // 拿到存贮在调用者里的值 + // 若调用者还没调 setMyNumber, 则值为 `0` return favoriteNumber[msg.sender]; } ``` -在这个小小的例子中,任何人都可以调用`setMyNumber`并在我们的合约中存下一个`单元(uint)`并且与他们的地址相绑定。 然后,他们调用“whatIsMyNumber”就会返回他们存储的“单元(uint)”。 +在这个小小的例子中,任何人都可以调用 `setMyNumber` 在我们的合约中存下一个 `uint` 并且与他们的地址相绑定。 然后,他们调用 `whatIsMyNumber` 就会返回他们存储的 `uint`。 -使用“msg.sender”很安全,因为它具有以太坊区块链的安全保障 - 除非窃取与以太坊地址相关联的私钥,否则是没有办法修改其他人的数据的。 +使用 `msg.sender` 很安全,因为它具有以太坊区块链的安全保障 —— 除非窃取与以太坊地址相关联的私钥,否则是没有办法修改其他人的数据的。 # 实战演习 -我们来修改第1课的`_createZombie`方法,将僵尸分配给函数调用者吧。 +我们来修改第1课的 `_createZombie` 方法,将僵尸分配给函数调用者吧。 -1.首先,在得到新的僵尸`id`后,更新`zombieToOwner`映射,在`id`下面存入`msg.sender`。 +1. 首先,在得到新的僵尸 `id` 后,更新 `zombieToOwner` 映射,在 `id` 下面存入 `msg.sender`。 -然后,我们为这个“msg.sender”名下的`ownerZombieCount`加1。 +2. 然后,我们为这个 `msg.sender` 名下的 `ownerZombieCount` 加 1。 -跟在javascript中一样, 在Solidity中你也可以用`++`使`uint`递增。 +跟在 JavaScript 中一样, 在 Solidity 中你也可以用 `++` 使 `uint` 递增。 ``` uint number = 0; diff --git a/zh/2/4-require.md b/zh/2/4-require.md index c5e7b5f7c..c4e7e05dd 100644 --- a/zh/2/4-require.md +++ b/zh/2/4-require.md @@ -1,5 +1,5 @@ --- -title: Require +title: 需求(Require) actions: ['checkAnswer', 'hints'] material: editor: @@ -85,39 +85,39 @@ material: } --- -在第一课中,我们成功让用户通过调用`createRandomZombie`并输入一个名字来创建新的僵尸。 但是,如果用户持续调用这个函数,并创建出无限多个僵尸加入他们的军团,这游戏就太没意思了! +在第一课中,我们成功让用户通过调用 `createRandomZombie` 并输入一个名字来创建新的僵尸。 但是,如果用户能持续调用这个函数来创建出无限多个僵尸加入他们的军团,这游戏就太没意思了! 于是,我们作出限定:每个玩家仅允许一次调用随机创建函数。 这样一来,新玩家可以在刚开始玩游戏时通过调用它,为其军团创建初始僵尸。 我们怎样才能限定每个玩家只调用一次随机创建函数呢? -答案是使用`require`。 `require`使得函数在某些状况下运行时候抛出异常,迫使程序终止: +答案是使用`require`。 `require`使得函数在某些状况下运行时候抛出错误,并停止继续执行: ``` function sayHiToVitalik(string _name) public returns (string) { // 比较 _name 是否等于 "Vitalik". 如果成立,抛出异常并终止程序 // (敲黑板: Solidity 并不支持原生的字符串比较, 我们只能通过比较 - // 两字符串的 keccak256 哈希码来进行判断) + // 两字符串的 keccak256 哈希值来进行判断) require(keccak256(_name) == keccak256("Vitalik")); - // 如果返回TRUE, 运行如下语句 + // 如果返回 true, 运行如下语句 return "Hi!"; } ``` -如果你使用参数`sayHiToVitalik(“Vitalik”)`调用创建函数 -,它会返回“Hi!”。而如果调用时候使用了其他参数,它则会抛出异常并停止运行。 +如果你使用参数 `sayHiToVitalik(“Vitalik”)` 调用创建函数 +,它会返回“Hi!”。而如果调用时候使用了其他参数,它则会抛出错误并停止继续执行。 -因此,在调用一个函数之前,用`require`验证前置条件是非常有必要的。 +因此,在调用一个函数之前,用 `require` 验证前置条件是非常有必要的。 -#实战演习 +# 实战演习 -在我们的僵尸游戏中,我们不希望用户通过反复调用`createRandomZombie`来給他们的军队创建无限多个僵尸 - 这将使得游戏非常无聊。 +在我们的僵尸游戏中,我们不希望用户通过反复调用 `createRandomZombie` 来給他们的军队创建无限多个僵尸 —— 这将使得游戏非常无聊。 -我们使用了`require`来确保这个函数只有在每个用户第一次调用它的时候运行,用以创建初始僵尸。 +我们使用了 `require` 来确保这个函数只有在每个用户第一次调用它的时候执行,用以创建初始僵尸。 -1.在`createRandomZombie`的前面放置`require`语句。 使得函数先检查`ownerZombieCount [msg.sender]`是否等于`0`,不然就抛出一个错误。 +1. 在 `createRandomZombie` 的前面放置 `require` 语句。 使得函数先检查 `ownerZombieCount [msg.sender]` 是否等于 `0` ,不然就抛出一个错误。 ->注意:在Solidity中,关键词放置的顺序并不重要 +>注意:在 Solidity 中,关键词放置的顺序并不重要 - 虽然参数的两个位置是等效的。 但是,由于我们的答案检查器比较呆板,它只能认定其中一个为正确答案 - 于是在这里,我们就约定把`ownerZombieCount [msg.sender]`放前面吧 \ No newline at end of file diff --git a/zh/2/5-inheritance.md b/zh/2/5-inheritance.md index 1d682bdfd..17e6838e1 100644 --- a/zh/2/5-inheritance.md +++ b/zh/2/5-inheritance.md @@ -1,5 +1,5 @@ --- -title: Inheritance +title: 继承(Inheritance) actions: ['checkAnswer', 'hints'] material: editor: @@ -95,7 +95,7 @@ material: 我们的游戏代码越来越长。 当代码过于冗长的时候,最好将代码和逻辑分拆到多个不同的合约中,以便于管理。 -有个让Solidity的易于管理的功能,就是** _inheritance_ **(集成)合约: +有个让 Solidity 的代码易于管理的功能,就是合约** _inheritance_ **(继承): ``` contract Doge { @@ -111,13 +111,13 @@ contract BabyDoge is Doge { } ``` -由于 `BabyDoge` 是从 “Doge”哪里 **_inherits_** (继承)过来的。 这意味着当您编译和部署了`BabyDoge`,它将可以访问`catchphrase()`和`anotherCatchphrase()`和其他我们在`Doge`中定义的其他公共函数。 +由于 `BabyDoge` 是从 `Doge` 那里 **_inherits_** (继承)过来的。 这意味着当您编译和部署了 `BabyDoge`,它将可以访问 `catchphrase()` 和 `anotherCatchphrase()`和其他我们在 `Doge` 中定义的其他公共函数。 -这可以用于逻辑继承(比如表达子类的时候,`Cat`是一种`Animal`)。 但也可以简单地将类似的逻辑组合到不同的类中以组织代码。 +这可以用于逻辑继承(比如表达子类的时候,`Cat` 是一种 `Animal`)。 但也可以简单地将类似的逻辑组合到不同的类中以组织代码。 -#实战演习 +# 实战演习 -在接下来的章节中,我们将要为僵尸实现各种功能,让它可以“猎食”和“繁殖”。 通过将这些运算放到父类“ZombieFactory”中,使得所有“ZombieFactory”的继承者类都可以使用这些方法。 +在接下来的章节中,我们将要为僵尸实现各种功能,让它可以“猎食”和“繁殖”。 通过将这些运算放到父类 `ZombieFactory` 中,使得所有 `ZombieFactory` 的继承者类都可以使用这些方法。 -1.在ZombieFactory下创建一个叫“ZombieFeeding”的合约,它是继承自“ZombieFactory”合约的。 +1. 在 `ZombieFactory` 下创建一个叫 `ZombieFeeding` 的合约,它是继承自 `ZombieFactory 合约的。 diff --git a/zh/2/6-importfiles.md b/zh/2/6-importfiles.md index 5852bc87d..3ef458987 100644 --- a/zh/2/6-importfiles.md +++ b/zh/2/6-importfiles.md @@ -1,5 +1,5 @@ --- -title: Import +title: 引入(Import) actions: ['checkAnswer', 'hints'] material: editor: @@ -65,9 +65,9 @@ material: 哇! 你有没有注意到,我们只是清理了下右边的代码,现在你的编辑器的顶部就多了个选项卡。 尝试点击它的标签,看看会发生什么吧! -代码已经够长了,我们把它分成多个文件以便于管理。 通常情况下,当Solidity项目中的代码太长的时候我们就是这么做的。 +代码已经够长了,我们把它分成多个文件以便于管理。 通常情况下,当 Solidity 项目中的代码太长的时候我们就是这么做的。 -在Solidity中,当你有多个文件并且想把一个文件导入另一个文件时,可以使用`import`语句: +在 Solidity 中,当你有多个文件并且想把一个文件导入另一个文件时,可以使用 `import` 语句: ``` import "./someothercontract.sol"; @@ -77,10 +77,10 @@ contract newContract is SomeOtherContract { } ``` -这样当我们在合约(contract)目录下有一个名为`someothercontract.sol`的文件(`./`就是同一目录的意思),它就会被编译器导入。 +这样当我们在合约(contract)目录下有一个名为 `someothercontract.sol` 的文件( `./` 就是同一目录的意思),它就会被编译器导入。 -#实战演习 +# 实战演习 -现在我们已经建立了一个多文件架构,并用`import`来读取来自另一个文件中合约的内容: +现在我们已经建立了一个多文件架构,并用 `import` 来读取来自另一个文件中合约的内容: -1.将`zombiefactory.sol`导入到我们的新文件`zombiefeeding.sol`中。 \ No newline at end of file +1.将 `zombiefactory.sol` 导入到我们的新文件 `zombiefeeding.sol` 中。 \ No newline at end of file diff --git a/zh/2/7-storage.md b/zh/2/7-storage.md index bf2038bf1..8a9e94fb7 100644 --- a/zh/2/7-storage.md +++ b/zh/2/7-storage.md @@ -1,5 +1,5 @@ --- -title: Storage vs Memory +title: 存储(Storage)与内存(Memory) actions: ['checkAnswer', 'hints'] material: editor: @@ -69,13 +69,13 @@ material: } --- -在Solidity中,有两个地方可以存储变量 - “存储”或“内存”。 +在 Solidity 中,有两个地方可以存储变量 —— `storage`(存储) 或 `memory` (内存)。 -**_Storage_**(存储)变量是指永久存储在区块链中的变量。 **_Memory_**(内存) 变量则是临时的,当外部函数对某合约调用完成时,内存型变量即被移除。 您可以把它想象成存储在你电脑的硬盘或是RAM中数据的关系。 +**_Storage_**(存储)变量是指永久存储在区块链中的变量。 **_Memory_**(内存) 变量则是临时的,当外部函数对某合约调用完成时,内存型变量即被移除。 你可以把它想象成存储在你电脑的硬盘或是RAM中数据的关系。 -大多数时候您都用不到这些关键字,默认情况下Solidity会自动处理它们。 状态变量(在函数之外声明的变量)默认为“存储”形式,并永久写入区块链;而在函数内部声明的变量是“内存”型的,它们函数调用结束后消失。 +大多数时候你都用不到这些关键字,默认情况下 Solidity 会自动处理它们。 状态变量(在函数之外声明的变量)默认为“存储”形式,并永久写入区块链;而在函数内部声明的变量是“内存”型的,它们函数调用结束后消失。 -然而也有一些情况下,您需要手动声明存储类型,主要用于处理函数内的** _ structs _ **和** _ arrays _ ** 时: +然而也有一些情况下,你需要手动声明存储类型,主要用于处理函数内的** _ structs _ **和** _ arrays _ ** 时: ``` @@ -90,32 +90,32 @@ contract SandwichFactory { function eatSandwich(uint _index) public { // Sandwich mySandwich = sandwiches[_index]; - // ^ Seems pretty straightforward, but solidity will give you a warning - // telling you you should explicitly declare `storage` or `memory` here. + // ^ 看上去很直接,不过 Solidity 将会给出警告 + // 告诉你应该明确在这里定义 `storage` 或者 `memory`。 - // So instead, you should declare with the `storage` keyword, like: + // 所以你应该明确定义 `storage`: Sandwich storage mySandwich = sandwiches[_index]; - // ...in which case `mySandwich` is a pointer to `sandwiches[_index]` - // in storage, and... + // ...这样 `mySandwich` 是指向 `sandwiches[_index]`的指针 + // 在存储里,另外... mySandwich.status = "Eaten!"; - // ...this will permanently change `sandwiches[_index]` on the blockchain. + // ...这将永久把 `sandwiches[_index]` 变为区块链上的存储 - // If you just want a copy, you can use `memory`: + // 如果你只想要一个副本,可以使用`memory`: Sandwich memory anotherSandwich = sandwiches[_index + 1]; - // ...in which case `anotherSandwich` will simply be a copy of the - // data in memory, and... + // ...这样 `anotherSandwich` 就仅仅是一个内存里的副本了 + // 另外 anotherSandwich.status = "Eaten!"; - // ...will just modify the temporary variable and have no effect - // on `sandwiches[_index + 1]`. But you can do this: + // ...将仅仅修改临时变量,对 `sandwiches[_index + 1]` 没有任何影响 + // 不过你可以这样做: sandwiches[_index + 1] = anotherSandwich; - // ...if you want to copy the changes back into blockchain storage. + // ...如果你想把副本的改动保存回区块链存储 } } ``` -如果您还没有完全理解究竟应该使用哪一个,也不用担心 -- 在本教程中,我们将告诉您何时使用“存储”或是“内存”,并且当您不得不使用到这些关键字的时候,Solidity编译器也发警示提醒您的。 +如果你还没有完全理解究竟应该使用哪一个,也不用担心 —— 在本教程中,我们将告诉你何时使用 `storage` 或是 `memory`,并且当你不得不使用到这些关键字的时候,Solidity 编译器也发警示提醒你的。 -现在,只要知道在某些场合下也需要您显式地声明“存储”或“内存”就够了! +现在,只要知道在某些场合下也需要你显式地声明 `storage` 或 `memory`就够了! # 实战演习 @@ -123,14 +123,14 @@ contract SandwichFactory { 当一个僵尸猎食其他生物体时,它自身的DNA将与猎物生物的DNA结合在一起,形成一个新的僵尸DNA。 -1.创建一个名为`feedAndMultiply`的函数。 使用两个参数:`_zombieId`(`单元(uint)`)和`_targetDna`(也是`uint`类型)。 设置属性为“public”(公开)的。 +1. 创建一个名为 `feedAndMultiply` 的函数。 使用两个参数:`_zombieId`(一个 `uint` )和` _targetDna` (也是 `uint` 类型)。 设置属性为 `public` 的。 -2.我们不希望别人用我们的僵尸去捕猎。 首先,我们确保对自己僵尸的所有权。 通过添加一个`require`语句来确保`msg.sender`只能是这个僵尸的主人(类似于我们在`createRandomZombie`函数中做过的那样)。 +2. 我们不希望别人用我们的僵尸去捕猎。 首先,我们确保对自己僵尸的所有权。 通过添加一个`require` 语句来确保 `msg.sender` 只能是这个僵尸的主人(类似于我们在 `createRandomZombie` 函数中做过的那样)。 ->注意:同样,因为我们的答案检查器比较呆萌,只认识把“msg.sender”在前的答案,如果您切换了参数的顺序,它就不认得了。 但您正常编码时,如何安排参数顺序都是正确的。 +>注意:同样,因为我们的答案检查器比较呆萌,只认识把 `msg.sender` 放在前面的答案,如果你切换了参数的顺序,它就不认得了。 但你正常编码时,如何安排参数顺序都是正确的。 -3. 为了获取这个僵尸的DNA,我们的函数需要声明一个名为myZombie的本地`僵尸'(这是一个“存储”形的指针)。 在我们的`zombies`数组中将这个变量设置为索引`_zombieId`。 +3. 为了获取这个僵尸的DNA,我们的函数需要声明一个名为 `myZombie` 的 本地`Zombie`(这是一个 `storage` 型的指针)。 将其值设定为和我们 `zombies` 数组中 `_zombieId`所指向的值。 -到目前为止,包括关闭`}`的那一行, 你该一共写了4行代码。 +到目前为止,包括关闭 `}` 的那一行, 你该一共写了4行代码。 下一章里,我们会继续丰富这个功能。 \ No newline at end of file diff --git a/zh/2/8-feedandmultiply2.md b/zh/2/8-feedandmultiply2.md index 97b768366..be8afc32d 100644 --- a/zh/2/8-feedandmultiply2.md +++ b/zh/2/8-feedandmultiply2.md @@ -76,9 +76,9 @@ material: } --- -我们来把`feedAndMultiply`函数写完吧。 +我们来把 `feedAndMultiply` 函数写完吧。 -获取新的僵尸DNA的公式很简单:计算吞噬者僵尸DNA和被吞噬目标DNA之间的平均值。 +获取新的僵尸DNA的公式很简单:计算猎食僵尸DNA和猎食目标DNA之间的平均值。 例如: @@ -91,16 +91,16 @@ function testDnaSplicing() public { } ``` -以后,我们也可以让函数变得更复杂些,比方给新的僵尸的DNA增加一些随机性之类的。但现在先从最简单的开始 - 以后还可以回来完善它嘛。 +以后,我们也可以让函数变得更复杂些,比方给新的僵尸的 DNA 增加一些随机性之类的。但现在先从最简单的开始 —— 以后还可以回来完善它嘛。 # 实战演习 -1.首先我们确保`_targetDna`不长于16位。要做到这一点,我们可以设置`_targetDna`为`_targetDna%dnaModulus`,并且只取其最后16位数字。 +1. 首先我们确保 `_targetDna` 不长于16位。要做到这一点,我们可以设置 `_targetDna` 为 `_targetDna%dnaModulus` ,并且只取其最后16位数字。 -2.接下来为我们的函数声明一个名叫 `newDna`的单元(uint),并将其值设置为myZombie的DNA和_targetDna的平均值(如上例所示)。 +2. 接下来为我们的函数声明一个名叫 `newDna` 的 `uint`,并将其值设置为 `myZombie`的 DNA 和 `_targetDna` 的平均值(如上例所示)。   - >注意:您可以用 myZombie.name 或 myZombie.dna 访问 myZombie的属性。 +>注意:您可以用 `myZombie.name` 或 `myZombie.dna` 访问 `myZombie` 的属性。 -计算出新的DNA,再调用 `_createZombie`就可以生成新的僵尸了。如果你忘了调用这个函数所需要的参数,您可以查看`zombiefactory.sol`选项卡。请注意,需要先给他命名,所以现在我们把新的僵尸的名字设为“NoName” - 我们回头可以编写一个函数来更改僵尸的名字。 +3. 一旦我们计算出新的DNA,再调用 `_createZombie` 就可以生成新的僵尸了。如果你忘了调用这个函数所需要的参数,可以查看 `zombiefactory.sol` 选项卡。请注意,需要先给它命名,所以现在我们把新的僵尸的名字设为`NoName` - 我们回头可以编写一个函数来更改僵尸的名字。 ->注意:对于 Solidity whizzes,你可能会注意到我们的代码在不太正确。别担心,下一章会解决这个问题的 ;) +>注意:对于 Solidity 高手,你可能会注意到我们的代码存在一个问题。别担心,下一章会解决这个问题的 ;) diff --git a/zh/2/9-internalfunctions.md b/zh/2/9-internalfunctions.md index fb69d2aaf..a41e2e156 100644 --- a/zh/2/9-internalfunctions.md +++ b/zh/2/9-internalfunctions.md @@ -106,17 +106,17 @@ material: 编译的时候编译器就会报错。 -错误在于,我们尝试从`ZombieFeeding`中调用`_createZombie`函数,但`_createZombie`却是'ZombieFactory`的`private`(私有)函数。这意味着任何继承自“ZombieFactory”的子合约都不能访问它。 +错误在于,我们尝试从 `ZombieFeeding` 中调用 `_createZombie` 函数,但 `_createZombie` 却是 `ZombieFactory` 的 `private` (私有)函数。这意味着任何继承自 `ZombieFactory` 的子合约都不能访问它。 -## 内部和外部 +## internal 和 external -除“public”和“private”属性之外,Solidity还使用了另外两个描述函数可见性的修饰词:`internal`(内部)和`external`(外部)。 +除 `public` 和 `private` 属性之外,Solidity 还使用了另外两个描述函数可见性的修饰词:`internal`(内部) 和 `external`(外部)。 -`internal`和 “private” 类似,不过, 如果某个合约继承自其父合约,这个合约即可以访问父合约中定义的“内部”函数。(嘿,这听起来正式我们想要的那样!)。 +`internal` 和 `private` 类似,不过, 如果某个合约继承自其父合约,这个合约即可以访问父合约中定义的“内部”函数。(嘿,这听起来正是我们想要的那样!)。 -`external`与`public`类似,只不过这些函数只能在合约之外调用 - 它们不能被合约内的其他函数调用。稍后我们将讨论什么时候使用`external`和`public`。 +`external` 与`public` 类似,只不过这些函数只能在合约之外调用 - 它们不能被合约内的其他函数调用。稍后我们将讨论什么时候使用 `external` 和 `public`。 -声明函数`internal`或`external`类型的语法,与声明`private`和`public`类型相同: +声明函数 `internal` 或 `external` 类型的语法,与声明 `private` 和 `public`类 型相同: ``` contract Sandwich { @@ -140,6 +140,6 @@ contract BLT is Sandwich { # 实战演习 -1. 将 `_createZombie()`函数的属性从 `private`改为 `internal` , 使得其他的合约也能访问到它。 - -我们已经成功把你的注意力集中在到`zombiefactory.sol`这个选项卡上啦。 +1. 将 `_createZombie()` 函数的属性从 `private` 改为 `internal` , 使得其他的合约也能访问到它。 + + 我们已经成功把你的注意力集中在到`zombiefactory.sol`这个选项卡上啦。 diff --git a/zh/2/template.md b/zh/2/template.md index 750cce698..a9c9b4230 100644 --- a/zh/2/template.md +++ b/zh/2/template.md @@ -1,5 +1,5 @@ --- -title: Data Types +title: 数据类型 actions: ['checkAnswer', 'hints'] material: editor: @@ -17,5 +17,5 @@ contract HelloWorld ``` -# 实战演练 +# 实战演习 diff --git a/zh/index.json b/zh/index.json index 9c40c72e6..ec8c63779 100644 --- a/zh/index.json +++ b/zh/index.json @@ -91,9 +91,9 @@ "Share this URL so your friends can check out your army:" ], "footer": [ - "Lesson 4 coming in 1-2 weeks", - "You'll get an email from us as soon as it's ready.", - "In the meantime, join us on {telegramLink}, or follow our {twitterLink} to join the conversation!" + "喜欢CryptoZombies? 跟我们打声招呼!", + "我们大家都在{telegramLink}, 或者可以查看{twitterLink}", + "少年!踏上你的第四课之路吧:" ], "zombieDesc": "A Level 3 CryptoZombie", "shareLinkText": "I just completed #CryptoZombies Lesson 3! Check out my zombie army:"