CryptoZombies Notes GitHub

oilsinwater / cryptoZombies.md

Install Saviour Oilsinwater/1AF4EEAEB6D91EA6829CCD57779D8A on your computer and use it on GitHu b-Desktop.

Precautions of crypto zombies

CryptoZombies.io Notes

Contracts

Solidt y-The code is concluded by a contract. In other words, all variables and functions belong to the contract and are considered the starting point of your plan. The following is an example of an empty contract called Helloworld.

HELLOWORLD contract

Version PRAGMA: Open the poster version of the Solidity compiler required to apply code;

PRAGMA SOLIDITY ^0. 4. 19; // Surple contract HELLOWORLD version compiler< // basic building block >

State Variables & Integers

Division condition: Every day, it is saved in the contract store incorporated into the Ethereum blockchain.

Think of it as a database record.

Contract sample< // This will be stored permanently in the blockchain uint myUnsignedInteger = 100; >

UINT: The image of the data regarded as an unconditional amount, that is, is meaningless.

& amp; amp; gt; Note: In the hardness UINT, this is a pseudonym of UINT256, 25 6-bit, and I am not aware of the total amount. You can announce UINT with the minimum number of bits, such as UINT8, UINT16, and UINT32. However, in general cases, it is rudimentary to apply UINT than any other case.

Operators

  • Addition: X + Y
  • Subject: X-Y
  • Multiplication: x * y
  • Salute: x / y
  • Module / Remainer: x % Y (for example, 13 % 5 is exactly the same 3, and if 5 is divided by 13, 3 is left over).

Solidity also supports index operators:

UINT x = 5 ** 2; // Same as 5^2 = 25 

Structure: Give the ability to create a certain amount

The structure is a harder data image for storing different properties. Is it similar to JavaScript objects?

Structure

Sisting string: The line is used for any length data in UTF-8 format. For example, String GreenTing = "Hello World!"

## Massive: Something collection

If you need some collection, you can apply an array. Solidity has two similarities in arrays: fixed arrays and dynamic sequences.

### Type of array

// UINT [2] Fixedarray; // Again, it is a fixed array, with the ability to maintain 5 lines: String [5] StringArray; // Dynamic arra y-has a function to increment every day without any fixed amount: UINT [] dynamicarray; // Person [] Perso n-structures array; // Dynamic array, you can increment every day. 

Considering the utility of the sequence of the structure on the blockchain, is the variable conditions are always stored in the blockchain? As a result, creating such a dynamic array of such a structure has the potential to be a database that is ideal for storing structured data in a contract.

Public Arrays

Person [] Puplic Peeple; 

Other contracts can read this array (cannot be written). In other words, this is a convenient pattern for storing public data in a contract.

Declare a function to solve the contract problem

This is an Eathamburgers function that requires two parameters, a string and int. For now, the textbook of the function is empty.

Function Eathamburgers (String_name, UINT_AMOUNT)< >Eathamburgers ("Vitalik", 100); // The function is called as follows. 

& amp; amp; gt; Note: In order to distinguish it from global variables, it is common to start the name of the functional parameter variable with the Andas Core (_) (not required). Use this rule in the tutorial.

## Handling structures and arrays

Creating new Structs

Structure Person< uint age; string name; >Person [] Public People; // Create new people: Person Satoshi = Person (172, "Satoshi"); // Add this person to array: People; 

It is also possible to create data and add to the structure in one line.

People. Push (Person (16, "vitalik");); 
UINT [] Numbers; Number. 

Private/Public Functions

In Solidity, the function is Public by default. This means that anyone (or in other contracts) can call your contract function and execute the code.

Of course, this is not always desirable, and the contract can be vulnerable to the attack. Therefore, it is a good idea to use the function to be private by default, and only what you want to publish is public.

Let's look at how to declare a private function:

UINT [] Numbers; Function _addtoArray (UINT_NUMBER) PRIVATE

This means that only other functions in the contract can call this function and add numbers to the array.

As you can see, I use the keyword Private after the function name. Like a function parameter, private function names usually start with the Andas score (_).

More on Functions

To return the value from the function, declares as follows:

String GreeTing = "What's Up Dog"; Function Sayhallo () Public Returns (String)

Solidity includes a function declaration of a return value (in this case, String).

For example, I do not change the value or write something. For example, I do not change the value or write something. Therefore, in this case, it can be declared as a Display function that means that it does not change just by looking at the data:

Function Sayhallo () Public Display Return (String)

Also, Solidity has quite a lot of functions, basically not, especially not accessing information in the application. Let's look at the right one:

FUNCTION _MULTIPLY (UINT A, UINT B) PRIVATE PURE RETURNS (UINT)

This function does not read data from the application status. As a result, in the given case, the function is declared as Pure.

AMP; gt; Note: It may be difficult to know the timing of marking the function as Pure/View. Fortunately, the Solidity Compiler provides a warning to know the timing of applying these modifiers.

## KECCAK256 and typing

The GeneraTerandomDNA function returns (half) random number int. How can I realize this?

Ethereum has an buil t-in hash function called KECCAK256, which is considered to be a SHA3 version. The hash function basically converts the input string to a random 25 6-bit hexadecimal. A small change in string leads to a major change in hash.

The hash function can be used for various purposes in Ethereum, but here it is used only to generate pseudo random numbers.

sample

// 6e91EC6bb462A4A6EE5AA2CB0E9CF7A052B0BA58B8748C00D2E5 KECCAK256 ("AAAB"); 010B7CF0E6ED451514981E58AA9 KECCAK256 ("Aaaac"); 

As you can see, the meaning of return is completely different, regardless of the change in input data of only one letter.

Note: It is quite troublesome to generate harmless random numbers on the blockchain. Our method is dangerous, but security is not considered an important value for zombie DNA, so it will be quite suitable for our purpose.

Typecasting to convert

A type cast is required for conversion between data type.

Let's look at the following example:

UINT8 a = 5; uint b = 6; // b return UINT and do not return UINT8, so return an error: UINT8 c = a * b; // To operate this in UINT8. Must be converted: UINT8 c = a * uint8 (b); 

In the above case, A * b returns UINT, but it may cause a problem because it tried to store it as UINT8. UINT8 works well, and the compiler does not get an error.

##compile

We will receive the name of the zombie as input and create a public function to create a zombie with a random DNA using the name.

Events

Action is a way to tell the contract to what actually happened on the blockchain.

The contract is almost completed! Let's add an event.

Actions are methods for contracts that announce that something has happened on the blockchain for your application, with the ability to "listen" for certain actions and determine the outcome when they occur.

function add(uint _x, uint _y) public. // Declare an IntegersAdded(uint x, uint y, uint result) event.< uint result = _x + _y; // fire an event to let the app know the function was called: IntegersAdded(_x, _y, result); return result; >

The front-end of your application can listen to this event. A javascript implementation would look like this:

YourContract. IntegersAdded(function(error, result))< // do something with result >

Web3.js

Web3. js Book Repository Ethereum Javascript

The joint contract is complete! Now we need to create a front-end in JavaScript that will communicate with the contract.

Ethereum has a JavaScript repository called Web3. js.

In the next lesson, we'll go into more detail on how to deploy a contract and how to configure Web3. js. But for now, let's look at a code example that shows how Web3. js interacts with an extended contract.

Don't worry if this doesn't make sense yet.

/ var abi = /* The abi generated by the compiler */ var ZombieFactoryContract = web3. eth. contract(abi) var contractAddress = /* The address of the contract on Ethereum after implementation */ var ZombieFactory = ZombieFactoryContract. at(contractAddress) // `ZombieFactory` contains access to the contract's public functions and events // Event listener for the word input method: $("#ourButton"). click(function(e)< var name = $("#nameInput").val() // Call our contract's `createRandomZombie` function: ZombieFactory.createRandomZombie(name) >) // Listen for the `NewZombie` event and update the UI var-event = ZombieFactory. NewZombie(function(error, result)) // Receive the zombie's DNA and update the UI.< if (error) return generateZombie(result.zombieId, result.name, result.dna) >) // Get the zombie's DNA and update the image function generateZombie(id, name, dna)< let dnaStr = String(dna) // pad DNA with leading zeroes if it's less than 16 characters while (dnaStr.length < 16) dnaStr = "0" + dnaStr let zombieDetails = < // first 2 digits make up the head. We have 7 possible heads, so % 7 // to get a number 0 - 6, then add 1 to make it 1 - 7. Then we have 7 // image files named "head1.png" through "head7.png" we load based on // this number: headChoice: dnaStr.substring(0, 2) % 7 + 1, // 2nd 2 digits make up the eyes, 11 variations: eyeChoice: dnaStr.substring(2, 4) % 11 + 1, // 6 variations of shirts: shirtChoice: dnaStr.substring(4, 6) % 6 + 1, // last 6 digits control color. Updated using CSS filter: hue-rotate // which has 360 degrees: skinColorChoice: parseInt(dnaStr.substring(6, 8) / 100 * 360), eyeColorChoice: parseInt(dnaStr.substring(8, 10) / 100 * 360), clothesColorChoice: parseInt(dnaStr.substring(10, 12) / 100 * 360), zombieName: name, zombieDescription: "A Level 1 CryptoZombie", >return zombieDetails & amp; amp; gt; 

Our javascript takes the semantics generated in zombieDetails above and replaces the image and CSS filters using some javascript browser magic (we're using Vue. js). We'll cover the full code for this in the appropriate lesson.

Msg.sender

Now that we have an equation to keep track of who has a zombie, we need to update the _createZombie method to take advantage of this.

To do this, we need to use the msg. sender. Zombie method.

msg.sender

  • msg. sender is a mass variable that points to the address that called the current function.

In Solidity, there are certain mass variables available to all functions. One of them is msg. sender, which refers to the address of the person (or smart contract) that called the current function.

Note: In robustness, the function's functionality should start with an outer challenge device. The contract starts doing something elementary on the blockchain and does nothing until someone triggers the 1 of that function. As a result, every time you are in jail MSG. Sender.

Here is a model of the application MSG. Sender and Reflector - Update:

mapping(address = & amp; amp; gt; uint)FavoriteNumber; function setMyNumber(uint _MyNumber) public< // Update our `favoriteNumber` mapping to store `_myNumber` under `msg.sender` favoriteNumber[msg.sender] = _myNumber; // ^ The syntax for storing data in a mapping is just like with arrays >function WhatsMyNumber() public view returns (UINT).< // Retrieve the value stored in the sender's address // Will be `0` if the sender hasn't called `setMyNumber` yet return favoriteNumber[msg.sender]; >

In this obvious case, each person is given the opportunity to call SetMyNumber and save a UINT associated with their address in the contract. If you follow WhatsMyNumber, it will return the saved UINT.

Using MSG. Sender ensures the security of the sender. Using Sender ensures the security of the Ethereum blockchain. The single way to change the external data is to kidnap the private resource associated with the Ethereum address.

Require

Use requirements to make the function discard the watch and stop execution if a certain condition is not met. This is similar to a relative operator; Boolean.

function sayhitovitalik (string _name) public returns (string)< // Compares if _name equals "Vitalik". Throws an error and exits if not true. // (Side note: Solidity doesn't have native string comparison, so we // compare their keccak256 hashes to see if the strings are equal) require(keccak256(_name) == keccak256("Vitalik")); // If it's true, proceed with the function: return "Hi!"; >

Inheritance

Instead of creating such a long contract all at once, sometimes you can split the logic of your code into several contracts to make your code more efficient.

One of the individual sanity that makes this more controllable is contract inheritance:

rain< function catchphrase() public returns (string) < return "So Wow CryptoDoge"; >Babydoge's contract is Doge.< function anotherCatchphrase() public returns (string) < return "Such Moon BabyDoge"; >> 

Babydoge inherits from Doge. This means that if you compile and extend Babydoge, you will have access to CatchPhrase() and another catchphrase() (and at least other social functions that can be modified with Doge).

This can be applied to logical inheritance (e. g. subclassing "cats are animals"). But it is also possible to apply code-elementary to group and organize relatedness into different similarities.

Import

Our code got quite long, so we split it in a certain number of files and organized it in a more controlled way. For example, in Solidity's own planning, the principle is to work with a long-term code base.

If you have a certain number of files and want to import one into another, solidity uses in the main text import:

import "./somethercontract. sol"; Contract A new contract is a number of other Contaracts.

Storage Vs. Memory

Solidity has two spaces in which it is possible to protect variables - storage and in-memory.

  • Storage is assumed to be variables that are saved to the blockchain every day.
  • In-memory variables are considered temporary and are washed during the challenge of the contract's external functions. Think of this as the strict disk of your computer and RAM.

In most cases, you do not need to apply these headers. This is because Sturdins processes by default. The variable status (a variable declared outside the function) is regarded as a repository by default and is written on the blockchain every day. On the other hand, variables from inside the function are regarded as memory and disappear when the function is terminated.

However, in some cases it is necessary to apply these headers, but only when structures and arrays are handled from inside the function:

SandwichFactory contract< struct Sandwich < string name; string status; >Sandwiches; Function Eatsandwich (UINT_index) Public< // Sandwich mySandwich = sandwiches[_index]; // ^ Above comment seems pretty straightforward, but solidity gives a warning // telling you that you should explicitly declare `storage` or `memory` here. // So instead, you should declare with the `storage` keyword, like: Sandwich storage mySandwich = sandwiches[_index]; // . in which case `mySandwich` is a pointer to `sandwiches[_index]` // in storage, and. mySandwich.status = "Eaten!"; // . this will permanently change `sandwiches[_index]` on the blockchain. // If you just want a copy, you can use `memory`: Sandwich memory anotherSandwich = sandwiches[_index + 1]; // . in which case `anotherSandwich` will simply be a copy of the // data in memory, and. 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] = anotherSandwich; // . if you want to copy the changes back into blockchain storage. >> 

This alone is enough, but in fact there are cases where you need to clearly save money and memory!

Zombie DNA

The formula for calculating the DNA of the fresh zombie is Ordinarna: This is a rudimentary average between the DNA of the nursing zombie and the target DNA.

Function Testdnaspling () Public< uint zombieDna = 2222222222222222; uint targetDna = 4444444444444444; uint newZombieDna = (zombieDna + targetDna) / 2; // ^ will be equal to 3333333333333333 >

Later, for example, if you try to add an accident to a fresh zombie DNA, the formula can be complicated. But for now, she keeps her in a state of unpleasant.

More on Function visibility

There is an error in the previous lesson code!

When trying to compile, the compiler makes an error.

The problem is basically trying to call the _Createzombie function from Zombiefeeding, but _Createzombie is regarded as a Private function from inside Zombiefactory. This, effectively, means that the contracted contracts have no opportunity to access.

Internal and External

In addition to Public and Private, Solidity has two similarities in functional visibility: Internal and EXTERNAL.

Internal is basically the same as PRIVATE, but the contract that inherits this function is available. (This is similar to what we need!).

External looks the same as Public, but these functions can only occur outside of the contract, and there is no possibility that other functions are generated from the inside of this contract. I will explain why EXTERNAL, not Public, will be explained later.

To explain internal and external functions, I will explain which syntax to actually use PRIVATE or Public:

Contract sandwich< uint private sandwichesEaten = 0; function eat() internal < sandwichesEaten++; >The BLT contract is a sandwich.< uint private baconSandwichesEaten = 0; function eatWithBacon() public returns (string) < baconSandwichesEaten++; // We can call this here because it's internal eat(); >

What Do Zombies Eat?

It's time to satisfy zombies! What is the most worshiped to eat zombies?

Coincidentally, I love eating crypto zombies.

To do so, it is necessary to read Kittydona from the smart contract cryptocyties. Cryptokities' data is not locked in the blockchain, so it can be arranged. Don't you think the blockchain is cool?

Don't worry. Our games do not harm cryptokiti. We just read Cryptokiti's data and cannot ship it.

Interacting with other contracts

In order for our contract to have another contract and submit ability on the blockchain, we need to modify the interface first.

Let's look at an unpretentious model. Assuming that the blockchain has the following contract:

Fitting contract< mapping(address =>(UINT) NUMBERS; function setnum (uint_num) Public< numbers[msg.sender] = _num; >Function Getnum (Address _myaddress) Public View Return value (UINT)< return numbers[_myAddress]; >> 

This is an unpretentious contract that can protect a personal blessed number, and he is connected to his Ethereum address. After that, others can use his address to find the lucky amount of this person.

Here, it is supposed that there is an external contract that wants to read the data in this contract with the support of the getnum function.

First, Luckynumbe r-You need to modify the contract interface:

Contract number interface

This is similar to a contract definition, but there are several differences. First of all, only declare the function you want to talk (GetNum in the provided case) and do not mention other functions or variables.

Second, we do not recognize the feature itself. Central brace (< and >) Instead, complete the poster of the beginner function using a semicolon (;).

Therefore, it looks like a skeleton of the contract. For example, a compiler will discover that this is an interface.

After incorporating this interface into the DAPP code, our contract will discover what the functions of another contract, what to call, and what to wait for.

The following lessons will explain how to call other contract functions, but before that, let's announce the Cryptokitties contract interface.

Using an Interface

Following the example of the previous number interface, the interface is defined as follows:

Contract number interface

To apply this interface to the contract:

Contract My Contract< address NumberInterfaceAddress = 0xab38. // ^ The address of the FavoriteNumber contract on Ethereum NumberInterface numberContract = NumberInterface(NumberInterfaceAddress); // Now `numberContract` is pointing to the other contract function someFunction() public < // Now we can call `getNum` from that contract: uint num = numberContract.getNum(msg.sender); // . and do something with `num` here >> 

Your contract will have at least the ability to communicate with other contracts on the Ethereum Blockchain.

Handling Multiple Return Values

This getkitty function is a model that returns a certain number of values ​​we saw first. Let's take a look at how to handle them:

Function MultipleReturns () Internal return (UINT A, UINT B, UINT C)< return (1, 2, 3); >Function Process MultiPlereturns () External< uint a; uint b; uint c; // This is how you do multiple assignment: (a, b, c) = multipleReturns(); >If you are only interested in // or one meaning: Function GetLasternValue () External< uint c; // We can just leave the other fields blank: (,,c) = multipleReturns(); >

Bonus: Kitty Genes

The function of the function is now complete. But let's add one bonus function.

Make sure that cat zombies have a certain unique personality that indicates that they are cat zombies.

To do so, you can add a special cat code to the zombi e-DNA.

Recall from lesson 1 that we are currently using only the first 12 digits of a 16-digit DNA to define the appearance of a zombie. So let's use the last two unused digits to define some "special" data.

For example, let's say the last two digits of a zombie cat's DNA are 99 (because cats have 9 lives). As a result, our code assumes that if a zombie is born from a cat, the last two digits of its DNA must be 99.

If statements

Solidity's if statement looks the same as in javascript:

function eatBLT(string sandwich) public< // Remember with strings, we have to compare their keccak256 hashes // to check equality if (keccak256(sandwich) == keccak256("BLT")) < eat(); >> 

Wrapping up Lesson 2

If you look at the demo on the right, you'll see this in action. Now, keep in mind that you can't wait until the end of this page 😉. Click on the kittens to attack them and see what new zombies will emerge!

Javascript implementation

When you're ready to deploy this contract to Ethereum, all you need to do is compile and implement ZombieFeeding. This contract is considered the final contract inheriting from ZombieFactory and has access to all public methods of both contracts.

Let's look at a sample of interaction with an extended contract supporting Javascript and web3. js:

var abi = /* The abi generated by the compiler */ var ZombieFeedingContract = web3. eth. contract(abi) var contractAddress = /* The address of the contract on Ethereum after implementation */ var ZombieFeeding = ZombieFeedingContract. at(contractAddress) // All we need to do is know the ID of the zombie and the ID of the cat we want to attack. let zombieId = 1; let kittyId = 1; // To get a picture of the CryptoKitty, we need to query the web interface. This information is not stored on the blockchain, only on the web server. // With everything stored on the blockchain, you don't have to worry about your server crashing, changing your personal API, or not being able to download assets if you don't like the zombie-themed game ;) 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 the cat: $(". kittyImage"). click(function(e)< // Call our contract's `feedOnKitty` method ZombieFeeding.feedOnKitty(zombieId, kittyId) >) // Listen to the NewZombie event from the contract to render: 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) >) 

Immutability of Contracts

So far, Solidity has been very similar to other languages ​​such as JavaScript. But there are actually a number of techniques that distinguish Ethereum DApps from regular applications.

Let's start with the fact that after a contract is implemented in Ethereum, it is immutable, meaning it has no ability to be changed or updated in any way.

The code you first adopt in your contract remains in the blockchain forever. This is one of the reasons why robustness is important. If your contract code has a flaw, you can't fix it later. You need to declare it to your users to start applying another address of the smart contract with the changes.

But this is also the personality of smart contracts. The code is law. If you read and check the code of the smart contract, you can't doubt that when you actually call the function, it creates what is actually prescribed in the code. No one can change this function and suddenly get the result.

External dependencies

In lesson 2, we aggressively coded the address of the Cryptokitties contract in our DAPP. But what if the Cryptokitties-agreement goes wrong and someone kills all the kittens?

This is highly unlikely, but if it did happen, our DAPP would become absolutely redundant - our DAPP would point to an aggressively coded address that would no longer return kittens. Our zombies would no longer be able to eat kittens, and we wouldn't be able to change the contract to fix it.

Based on this, there is often value in owning functions that can update important parts of a DAPP for you.

For example, in return for actively encrypting the address of the Cryptokitties contract in our DAPP, we must own the SetKittyContractDaddress function.

Ownable Contracts

Did you notice the security hole in the previous section?

The SetKittyContractAddress function is considered an external function and can be called by anyone! This basically means that anyone who calls this function has the ability to change the address of the Cryptokitties contract, ruining our application for all users.

We want to own the opportunity to update this address in the contract, but since we don't, everyone has the opportunity to update it.

To overcome in a similar sphere, one all-manufactured practice was pointed out - to make their own contract - this means that they have an owner (you), who owns a special interest.

OpenZeppelin's Ownable contract

Below is a proprietary treaty excerpted from the Openzeppelin Solidity Library. Openzeppelin is a book-length collection of non-modest, experienced smart contracts that you can apply to your own personal DAPP. Next, in this class, I gave a brief advice to visit their website to continue the training!

Please read the following agreement. There will be some things that you have not studied yet, but don't worry, we will talk about them later.

/** * @title ownable * @dev Ownable contracts include the owner's address and provide basic authority management functions. */ Contract Ownable< address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ function Ownable() public < owner = msg.sender; >/*** @dev produced when called from an account other than the owner. */ ONLYOWNER () Model< require(msg.sender == owner); _; >/*** @dev allows the current owner to transfer the contract control to a new owner. * @Param NEWOWNER The address of transferring ownership. */ Function Transferownership (Address New Owner) Public owner only.< require(newOwner != address(0)); OwnershipTransferred(owner, newOwner); owner = newOwner; >> 

There are some new things that I have never seen before:

  • Constractor: Function Ownable () is a constructor that returns a special function of options with the same name as the contract itself. This contract is satisfied only once when the contract is created for the first time.
  • Function Model: Onlyowner () Modern. Modifier is a sem i-functional family used to convert other functions and is usually used to test specific claims before execution. In this case, ONLYOWNER can be used to limit access so that only the owner of the contract can start this function. The function modifier will be explained in detail in the related chapter;
  • The main text has an index.

The leader's Ownable contract prepares such a correct thing:

  1. When the contract is formed, the constructor specifies the owner of the msg. sender (implemented).
  2. Add a migrant ONLYOWNER. This modification has a function to limit access to specific functions only to the owner.
  3. This allows you to transfer the contract to a new owner.

Since ONLIOWNER is often used for contracts, most of the Solidity Dapps are copied/ paste this Ownable contract, and the first contract is inherited from this contract.

I want to limit SetkittyContractaddress to Owner only, so create the same thing for the contract.

onlyOwner Function modifier

Since the base contracts inherit Zombieefactory from Ownable, the onlyowner function modifier can be applied to Zombiefeeding.

This is related to the mechanism of inheritance of the contract. I want you to remember:

Zombiefeeding is ZombieFactory Zombievactory is Ownable 

So Zombiefeeding is still considered Ownable and has the ability to access functions / activities / corrections from Ownable contracts. This will also be applied to the contracts acquired from Zombiefeeding in the future.

Function Modifiers

For example, a function's function looks like a function, but in exchange for it uses a modifier's main clause. And it's not possible to invoke it directly as a function. Instead, you can add the name of a modifier to the end of the function's clause to change its behavior.

Let's look at a single tower in a little more detail:

/*** @dev produced when called from an account other than the owner. */ ONLYOWNER () Model< require(msg.sender == owner); _; >Start applying this modifier.< event LaughManiacally(string laughter); // Note the usage of `onlyOwner` below: function likeABoss() external onlyOwner < LaughManiacally("Muahahahaha"); >> 

Note the onlyOwner modifier on LikeAboss. When you call LikeAboss, it creates a code at the beginning of the inner-city. After this, when the operator _; is specified, it goes back to onlyOwner and creates a code from inside LikeAboss.

There are other ways to use modifiers, but one of the more common ways is to add a frisky-test of require before the function.

In the case of Soultowner, adding this modifier to the function prepares it so that only the contract's owner (you, if you implement it) has the ability to call this function.

Note: As is often the case, it is necessary to set special opportunity owners for the contract, but this can still be executed maliciously. For example, the owner has the ability to add a "black move" function, broadcasting any zombies to himself!

So, fundamentally, you need to remember that, in fact, that the Dapp is running on Ethereum, does not mean that it is actually mechanically decentralized - you need to read the absolute initiation code for you, to ensure that it is not subject to special control by the owner of the contract that you may have to worry about. Creators are obligated to observe the balance between being careful and maintaining control over the data of the DAPP.

Gas

Great! Now we know how to update the important parts of the DAPP. Now we can prevent other users from participating in our contract.

Let's see how solidity stands out from other programming languages:

Gas = DApps fuel

In solidity, every time a user creates a function on your DAPP, they must pay using a unit of currency titled gas. Users get gas in Ether (Ethereum's currency), and as a result, users must spend ETH to execute functions on your DAPP.

The amount of gas that is important for the performance of the function depends on the difficulty of the logic. Individual calculations include gas bills, and the gas fee relys on the calculation resources required to implement it (for example, records in memory, rather than building two integrated numbers. It is much more expensive). The total gas fee for your function is the amount of gas charges for all individual activities.

The start of the function is to sacrifice users with actual resources, so optimization of code is much more important in Ethereum than other programming languages. If your code is inaccurate, the user must pay for the performance of your functio n-and this will lead to a million dollar costs for thousands of users. I have a possibility.

Why is gas necessary?

Ethereum is like a huge, slow, but very modest computer. When you execute a function, all nodes on the network need to launch the same function to get the result s-thousands of components that control each function of functions have distributed Ethereum and the data is changed. There is no resistance to censorship.

The creator of Ethereum even believed that someone would not be able to clog the network in an unlimited cycle or use all network resources for stressful calculations. As a result, the transactions were not free, and users had to pay for calculating and saving data.

Note: This does not apply to side chans as Cryptozombies creators are created by Loom Network. Perhaps it is not important to launch this game like World of Warcraft. However, you can start with Sidchan with different consensus methods. The following lessons will explain in the following lessons about which type of DApps is deployed in Sidchan and which type of Dapps is deployed in Ethereum, which is the main network.

Struct packing to save gas

Lesson 1 mentioned that there are other types of UINT, such as UINT8, UINT16, and UINT32.

Usually, the introduction of these subtypes rarely reveals practicality, as the robust character is automatically secured a 25 6-bit memory from the UINT volume. For example, even if UINT8 is introduced instead of UINT (UINT256), the petrol will not be saved.

However, there are exceptions: from the structure.

If you have a certain amount of UINT from the inside of the structure, the introduction of UINT is the minimum volume, and if it is very likely, it can pack these variables together. , They take less space. for example

Structure NORMALSTRUCT< uint a; uint b; uint c; >Structure minim< uint32 a; uint32 b; uint c; >// NormalStruct Normal = NormalStruct (10, 20, 30); Minime Mini = Minime (10, 20, 30); 

Based on this, we try to apply a small integer subtype with all possibilities from inside the structure.

Also, by grouping similar data types (i. e. placing them near other data types and friends in the structure), we ensure that robustness has the ability to minimize the storage required. For example, a design with UINT C field; uint32 a; uint32 b; will rent less space than a design with UINT32 A field; uint c; uint32 b; because the UINT32 fields are grouped together.

Level ownership does not inhibit statements. Later, when we organize the contraction system, zombies that win more battles will increase their personal grade in the period and gain access to huge opportunities.

Side-time real estate calls for some more comments. This task was done to add a "cooling off period". A "cooling off period" is the time required for a zombie to wait or attack before it can eat or attack again. Without this, a zombie would have 1000 chances to attack and reproduce a day.

To observe how long it takes for a zombie to be able to attack again, we can apply a unit of solid time.

Time units

Solidity gives a constant individual unit.

The now variable returns the current UNIX temporal label (the number of seconds adopted since January 1, 1970). As of the time of writing this comment, -time in UNIX is still 1515527488.

Note: Unix time is usually managed in 32 bits. This leads to the "year 2038" task, where a 32-bit short-term UNIX marker would overcome and break a large number of legacy systems. As a result, if you want your DAPP to continue functioning for 20 years, you can apply a 64-bit amount. Design Solution!

Solidity has a few more time units: seconds, minutes, hours, days, weeks, and years. These are converted to UINT - the number of seconds in the period, i. e. 1 minute is 60, 1 hour is 3600 (60 seconds x 60 minutes), 1 day is 86400 (24 hours x 60 minutes x 60 seconds), etc.

Here are some examples where such time units might be useful:

Uint last dated; // Disambiguate `LastUpdated` from Now` Function UpdateMestamp () Public // Returns `true`.< lastUpdated = now; >// Returns `true` if 5 minutes have passed since the call to updateatetimestamp`, `false' if not. Function FiveMinuteshaved () Public View returns is not executed< return (now >= (LastUpdated + 5 minutes)); & amp; amp; gt; 

We can now apply these time units to our zombie freezing function.

Zombie Cooldowns

Now that we have set the readyTime property in our zombie struct**, go to zombiefeeding. sol and sell the decay timer.

Change feedAndMultiply to this:

  1. feed starts the timer for freezing zombies.
  2. Zombies don't have a chance to eat kittens until the freezing phase is over.

This will prevent zombies from feeding on kittens indefinitely and breeding all day long. In the future, when we add combat functionality, we will make attacking other zombies dependent on the freezing duration.

First, we define some preliminary functions to set and check the ready time of zombies.

Passing structs as arguments

You can pass references to structures as arguments to personal and internal functions. This is useful for transferring zombie structures between functions, for example.

The syntax is:

function _doStuff(zombie story _zombie) internal< // do stuff with _zombie >

You can pass a link to the zombie to broadcasts or functions to find zombie IDs.

Public Functions & Security

Now let's modify feedAndMultiply for the freeze timer.

Looking at this function, we can see that we actually exposed it in the previous lesson. For security, what you need to do is look at all public or external functions and think about how users can misuse them. Remember: if these functions did not have modifiers like OnlyOwner, any user could call them and send them any kind of data.

Given this special function, we can say that users have the ability to call this function directly and pass in any kind of _targetDna or _species. This is not like a game. We want them to follow our rules!

Upon closer inspection, we see that this function should only be called by the feedOnKitty() function. In fact, the easiest way to prevent similar abuse is to make this function internal.

More on Function Modifiers

Great! Now we have an active zombie freezing timer.

Next, we will add some fallback methods. Create a new file called zombiehelper. sol and import zombiefeeding. sol. This will help us organize our code.

Let's make our zombies get special abilities when they reach a certain value. But to do that, we need to know a bit more about function modifiers first.

Function modifiers with arguments

Earlier, we explained a simple example of OnlyOwner. However, function modifiers may still inherit arguments. For example

// Reflection for storing the user's age: Mapping (UINT = & amp; gt; uint) Public age; // Modifire requires that this user is higher than a specific age: Modifire OlderThan ( UINT_age, UINT_USERID)< require(age[_userId] >= _age); _; & amp; amp; gt; // To drive a car (at least in the United States) must be 16 years old. Function DriveCar (UINT_USERID) Public ElderThan (16, _userid) // You can call the `OlderThan` modulator with such arguments.< // Some function logic >

Here, the OlderThan modulator seems to accept arguments like the function. And in fact, the DriveCar function passes its own argument to the modifier.

Let's create a modifier that restricts access to special abilities using zombie value properties.

Zombie Modifiers

Let's create a function using aboutLevel Modifier.

In our game, there are some incentives to raise your zombie level:

-You can change the name of a zombie with a level 2 or higher. -You can provide custom DNA for zombies with level 20 or more.

We sell these functions below. For reference, I will show the code example of the previous lesson:

// Reflection for storing the age of the user: Mapping (UINT = & amp; amp; gt; uint) public age must be higher than a specific age: Modifier OlderThan (UINT_AGE, UINT _userid)< require (age[_userId] >= _age); _; & amp; amp; amp; gt; // To drive a car (at least in the United States) Function DriveCar (UINT_USERID) Public ElderThan (16, _USERID)< // Some function logic >

Saving Gas With 'View' Functions

amazing! We now have several special abilities for zombies larger than the highest price. If you want to do it, you can add more such features.

Let's add another function: Dapp requires a way to display the entire zombie corps of the user.

Since this function only loads data from the blockchain, it can be a viewing function. The topic necessary to talk about gas cost optimization:

View functions don't cost gas

The viewing function does not take one penny when it is called by a user from the outside.

This is because the lookup function does not actually replace something in the blockchain, but only reads the data. By labeling the function as a view, web3. js only needs to require your local Ethereum node to execute the function, but do not need to execute transactions on the blockchain. I will tell you (this must be started on each node and it will be gased.).

The setup of Web3. js using personal nodes will be explained later. But at the moment, the use of an external view function for reading as much as possible can improve DAPP gasoline consumption for your user.

Note: If the presentation function is called from another function in the same contract that is not considered a presentation function, it will cost gas in exactly the same way. This is because another function is actually running the transaction on Ethereum and needs to be inspected by each node as well. Therefore, the view function is only free while it is called externally.

Storage is Expensive

One of the most expensive operations in Solidity is implementing storage, especially recording.

Because every time you write or modify data, it is recorded in the blockchain forever. Forever! 1, 000 nodes around the world will have to store this data on their respective hard drives, and as the blockchain matures, this data size will continue to grow over time. This is why you need to be strict with certain expenditures.

To reduce costs, you ignore writing data to storage only when absolutely necessary. Sometimes this is due to seemingly inefficient programming logic. For example, like reconstructing an array in memory only to store it in a variable for quick lookup when a function is called to replace the array.

Looking up large amounts of data is very expensive in most programming languages. But in Solidity, it is much more cost-effective than implementing storage in an external view function. (Because view functions don't cost the user gas.)

We'll cover for loops in another chapter, but before that, let's look at how to advertise an in-memory array.

Declaring arrays in memory

With a main memory array, you can create a new array from a function without writing anything to memory. Since the array only exists until the end of the function call, this is much more beneficial gas-wise for a presentation function that is called externally than updating an array in storage.

This is how you can read an in-memory array:

function getArray() external pure return(uint[])< // Instantiate a new array in memory with a length of 3 uint[] memory values = new uint[](3); // Add some values to it values.push(1); values.push(2); values.push(3); // Return the array return values; >

This is an obvious example, just to demonstrate the syntax, but in the appropriate chapter we'll look at its intricacies with a for loop that assumes a real usage scenario.

Note: memory arrays must be created with a length argument (3 in the given case). In real time, they have no chance of being changed to a volume, as storing an array with the support of array. push() does, but this may change in future versions of Solidity.

For Loops

In the previous chapter, we mentioned that there are cases when you need to use a for loop to build the contents of an array into a function, rather than simply storing the array in storage.

Let's see why that is.

The reliable implementation for our function, Getzombiesbyowner will save zombies-the protection of the zombies on the convention of zombie factor-decomposition:

Mapping (Address = & amp; amp; gt; uint []) Public Property 

Then, when creating a new zombie, use Ownertozombies [Owner]. Push to the owner's zombie. And GetzombiesByowner is a pretty decorative function:

Function GetzombiesByower (Address _Athner) External View Returns (UINT [])

The problem with this approach

Such situations are simple and attractive. However, if you add a function to transfer zombie from the first owner to another owner, let's look at what happens (let's add it to the appropriate class!).

This mobile function is required:

  1. Transfer zombies from new owners to owners,
  2. Remove zombies from the property of the old owner,
  3. Move all the zombies in the lon g-term owner's squares to one space and fill the gap.
  4. Reduce the length of the array by one.

Step 3 is quite inaccurate in terms of gas because it is necessary to execute each zombie record. If the owner has 20 zombies and the first zombie is replaced, 19 records must be placed to maintain a large order.

Records in the repository are one of the most costly solidity activities, so it costs gas every time you call this acceleration function. Even worse, every time you call, the number of zombies of the user's army and the zombie index, which is a personal trading, will cost different amounts of gas. Thus, users do not become aristocrats about how much gas they send.

Note: Of course, there is an option to convert the last zombie of the array, fill the missing connector, and reduce the unit length of the array. However, during this time, when we took the function, we would have changed the order of the army zombies.

Since the viewing function is not worth gas during the external call, we can apply the FORBIESBYWNER-cycle to the overall zombie-array and zombies belonging to a specific owner. During this time, we do not need to limit the arrays in the storage, so our transmission functions become much more useful, and this, how far, this tuning generally becomes more profitable.

Using for loops

In the solid cycle, the syntax is similar to JavaScript.

Consider a sample that creates an even number:

Function GETEVENS () Pure External Returns (UINT [])< uint[] memory evens = new uint[](5); // Keep track of the index in the new array: uint counter = 0; // Iterate 1 through 10 with a for loop: for (uint i = 1; i >Return value 

This function returns an array of [2, 4, 6, 8, 10].

Wrapping It Up

Congratulations! This completes the task 3.

  • A method of updating the Cryptokiti Terms has been added.
  • I learned to protect the main features with the support of ONLYOWNER.
  • I learned about gas optimization.
  • The meaning and exposure time have been added to the zombie.
  • When the zombies are higher than the specific value, the name of the zombie and the function to update the DNA have been created.
  • Finally, there is a function to return the job zombie army.

Claim your reward

With the advantage of passing Lesson 3, both zombies have raised their individual level!

And now, if the Noname (cat zombies performed in lesson 2) increases to double, you can call Changename and name it. Noname is no longer!

Noname names, proceed to the right chapter and complete the task.

Payable

So far, we have explained quite a lot of functional modifiers. It's difficult to understand everything, so let's spend it on a short education program:

  1. Private means that it may only be caused by other functions from inside the contract. External can only be caused outside the contract.
  2. Since the function interacts with the blockchain, the qualifiers of the conditions are still talking to us. Pure tells us not only to protect data from blockchain, but also not to read data from blockchain. Both functions do not require strong gas costs if they are called outside the contract (but if they are triggered by different functions from the inside, they will strongly require gas costs).
  3. Next, there is a user qualifier learned in Lesson 3:, for example, an overview and Avoore Bell. For these, modifying the user logic allows you to modify how they affect the function.

All of these modifiers can be folded together to determine the function in the future:

Function Test () External view ONLYOWNER different model< /* . */ >

This chapter introduces another function of the function.

The payable Modifier

Payable functions are part of the sturdy and Ethereum prepared for this coolness.

Let's remember. If an API function is caused by a simple web server, it is not possible to send US dollars in conjunction with bitcoin calls like functions.

But in Ethereum, since both the money (Ether), the data (the transaction payload) and the contract code live in Ethereum, you can call a function and pay money to the contract at the same time.

This allows you to implement some very interesting logic, like requiring a specific payment for the contract to execute the function.

Let's look at an example

OnlineStore Contract< function buySomething() external payable < // Check to make sure 0.001 ether was sent to the function call: require(msg.value == 0.001 ether); // If so, some logic to transfer the digital item to the caller of the function: transferThing(msg.sender); >> 

Here, msg. value is a method that knows how many Ethers were sent to the contract, where Ether is the cumulative number of measurements.

Here is what happens right: someone calls the function from web3. js (from the JavaScript frontend of the DApp) like this:

We expect //OnlineStore` to refer to an Ethereum contract. 

Note in the background of the value that the javascript function call shows the amount of Ether to send (0, 001). If you think of the transaction as an envelope, and the attributes you send to the function call are the contents of the message to put inside, adding meaning is the same as when you put cash into the envelope: the message and the money are delivered together to the recipient.

& amp; amp;> Note: If the function is not marked as paid and you try to send Ether as described above, the function will reject the transaction.

Zombie Contract versions

zombiehelper.sol - v6

pragma solidity ^0. 4. 19; import "./zombiefeeding. sol"; contract ZombieHelper is ZombieFeeding = _level); _; & amp; amp; gt; // 2. Insert the levelUp function here function levelUp(uint _zombieId) external payment function changeName(uint _zombieId, string _newName) external upperLevel(2, ieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) function getZombiesByOwner(address _owner) external return(uint[]) return result; & amp; amp; gt; & amp; amp; gt; & amp; amp; gt; & amp; amp; gt; pragma solidity ^0. 4. 19; import "./zombiefeeding. sol"; contract ZombieHelper is ZombieFeeding = _level); _; & amp; amp; gt; function changeName(uint _zombieId, string _newName) external upperLevel(2, _zombieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) // function get ZombiesByOwner(address _owner) external view return(uint[]) & amp; amp; gt; return result; & amp; amp; gt; & amp; amp; & amp; amp; gt; & amp; amp; & amp; amp; gt; function changeName(uint _zombieId, string _newName) external upperLevel(2, _zombieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) function getZombiesByOwner(address _owner) external view return(uint[])< // 1. Define levelUpFee here uint levelUpFee = 0.001 ether; modifier aboveLevel(uint _level, uint _zombieId) < require(zombies[_zombieId].level >PRAGMA SOLIDITY ^0. 4. 19; Import "./zombiencing. Sol"; Contract Zombiehelper is zombieeding< require(msg.value == levelUpFee); // requires base value for msg zombies[_zombieId].level++; // increments the zombie's level >= _Level); _; & amp; amp; gt; function Changename (UINT_ZOMBIEID, STRING_NEWNAME) EXTERNAL ABOVELEVEL (2, _zombieid)< require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].name = _newName; >Function ChangedNA (UINT_ZOMBIEID, UINT_NEWDNA) External level (20, _zombieid)< require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].dna = _newDna; >// Function Getzombiesbyowner (UINT_OWNER) External view return value (UINT [])< uint[] memory result = new uint[](ownerZombieCount[_owner]); uint counter = 0; for (uint i = 0; i < zombies.length; i++) < if (zombieToOwner[i] == _owner) < result[counter] = i; counter++; >PRAGMA SOLIDITY ^0. 4. 19; Import "./zombiencing. Sol"; Contract Zombiehelper is zombieeding 

zombiehelper.sol - v5

pragma solidity ^0. 4. 19; import "./zombiefeeding. sol"; contract ZombieHelper is ZombieFeeding = _level); _; & amp; amp; gt; // 2. Insert the levelUp function here function levelUp(uint _zombieId) external payment function changeName(uint _zombieId, string _newName) external upperLevel(2, ieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) function getZombiesByOwner(address _owner) external return(uint[]) return result; & amp; amp; gt; & amp; amp; gt; & amp; amp; gt; & amp; amp; gt; pragma solidity ^0. 4. 19; import "./zombiefeeding. sol"; contract ZombieHelper is ZombieFeeding = _level); _; & amp; amp; gt; function changeName(uint _zombieId, string _newName) external upperLevel(2, _zombieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) // function get ZombiesByOwner(address _owner) external view return(uint[]) & amp; amp; gt; return result; & amp; amp; gt; & amp; amp; & amp; amp; gt; & amp; amp; & amp; amp; gt; function changeName(uint _zombieId, string _newName) external upperLevel(2, _zombieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) function getZombiesByOwner(address _owner) external view return(uint[])< modifier aboveLevel(uint _level, uint _zombieId) < require(zombies[_zombieId].level >Function ChangedNA (UINT_ZOMBIEID, UINT_NEWDNA) External ABOVELEVEL (20, _zombieid)< require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].name = _newName; >Function ChangedNA (UINT_ZOMBIEID, UINT_NEWDNA) External level (20, _zombieid)< require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].dna = _newDna; >= _Level); _; & amp; amp; gt; & amp; gt;< uint[] memory result = new uint[](ownerZombieCount[_owner]); // Start here uint counter = 0; // keep track of the index in our results array for (uint i = 0; i < zombies.length; i++) < if(zombieToOwner[i] == _owner) < result[counter] = i; counter++; >PRAGMA SOLIDITY ^0. 4. 19; Import "./zombiencing. Sol"; Contract Zombiehelper is zombieeding 

zombiehelper.sol - v4

pragma solidity ^0. 4. 19; import "./zombiefeeding. sol"; contract ZombieHelper is ZombieFeeding = _level); _; & amp; amp; gt; // 2. Insert the levelUp function here function levelUp(uint _zombieId) external payment function changeName(uint _zombieId, string _newName) external upperLevel(2, ieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) function getZombiesByOwner(address _owner) external return(uint[]) return result; & amp; amp; gt; & amp; amp; gt; & amp; amp; gt; & amp; amp; gt; pragma solidity ^0. 4. 19; import "./zombiefeeding. sol"; contract ZombieHelper is ZombieFeeding = _level); _; & amp; amp; gt; function changeName(uint _zombieId, string _newName) external upperLevel(2, _zombieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) // function get ZombiesByOwner(address _owner) external view return(uint[]) & amp; amp; gt; return result; & amp; amp; gt; & amp; amp; & amp; amp; gt; & amp; amp; & amp; amp; gt; function changeName(uint _zombieId, string _newName) external upperLevel(2, _zombieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) function getZombiesByOwner(address _owner) external view return(uint[])< modifier aboveLevel(uint _level, uint _zombieId) < require(zombies[_zombieId].level >Function ChangedNA (UINT_ZOMBIEID, UINT_NEWDNA) External ABOVELEVEL (20, _zombieid)< require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].name = _newName; >Function ChangedNA (UINT_ZOMBIEID, UINT_NEWDNA) External level (20, _zombieid)< require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].dna = _newDna; >// Function Getzombiesbyowner (UINT_OWNER) External view return value (UINT [])< // Start here uint[] memory result = new uint[](ownerZombieCount[_owner]); return result; >> 

zombiehelper.sol - v3

pragma solidity ^0. 4. 19; import "./zombiefeeding. sol"; contract ZombieHelper is ZombieFeeding = _level); _; & amp; amp; gt; // 2. Insert the levelUp function here function levelUp(uint _zombieId) external payment function changeName(uint _zombieId, string _newName) external upperLevel(2, ieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) function getZombiesByOwner(address _owner) external return(uint[]) return result; & amp; amp; gt; & amp; amp; gt; & amp; amp; gt; & amp; amp; gt; pragma solidity ^0. 4. 19; import "./zombiefeeding. sol"; contract ZombieHelper is ZombieFeeding = _level); _; & amp; amp; gt; function changeName(uint _zombieId, string _newName) external upperLevel(2, _zombieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) // function get ZombiesByOwner(address _owner) external view return(uint[]) & amp; amp; gt; return result; & amp; amp; gt; & amp; amp; & amp; amp; gt; & amp; amp; & amp; amp; gt; function changeName(uint _zombieId, string _newName) external upperLevel(2, _zombieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) function getZombiesByOwner(address _owner) external view return(uint[])< modifier aboveLevel(uint _level, uint _zombieId) < require(zombies[_zombieId].level >Function ChangedNA (UINT_ZOMBIEID, UINT_NEWDNA) External ABOVELEVEL (20, _zombieid)< require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].name = _newName; >Function ChangedNA (UINT_ZOMBIEID, UINT_NEWDNA) External level (20, _zombieid)< require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].dna = _newDna; >// 2. `_Isready` Function _IsReady (Zombi e-Store _zombie) Inside (Bool) (Bool)< >> 

zombiehelper.sol - v2

pragma solidity ^0. 4. 19; import "./zombiefeeding. sol"; contract ZombieHelper is ZombieFeeding = _level); _; & amp; amp; gt; // 2. Insert the levelUp function here function levelUp(uint _zombieId) external payment function changeName(uint _zombieId, string _newName) external upperLevel(2, ieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) function getZombiesByOwner(address _owner) external return(uint[]) return result; & amp; amp; gt; & amp; amp; gt; & amp; amp; gt; & amp; amp; gt; pragma solidity ^0. 4. 19; import "./zombiefeeding. sol"; contract ZombieHelper is ZombieFeeding = _level); _; & amp; amp; gt; function changeName(uint _zombieId, string _newName) external upperLevel(2, _zombieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) // function get ZombiesByOwner(address _owner) external view return(uint[]) & amp; amp; gt; return result; & amp; amp; gt; & amp; amp; & amp; amp; gt; & amp; amp; & amp; amp; gt; function changeName(uint _zombieId, string _newName) external upperLevel(2, _zombieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) function getZombiesByOwner(address _owner) external view return(uint[])< modifier aboveLevel(uint _level, uint _zombieId) < require(zombies[_zombieId].level >PRAGMA SOLIDITY ^0. 4. 19; IMPORT "./OWNABLE. SOL"; Contract ZombieFactory is Owned< require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].name = _newName; >Function ChangedNA (UINT_ZOMBIEID, UINT_NEWDNA) External level (20, _zombieid)< require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].dna = _newDna; >> 

zombiehelper.sol - v1

pragma solidity ^0. 4. 19; import "./zombiefeeding. sol"; contract ZombieHelper is ZombieFeeding = _level); _; & amp; amp; gt; // 2. Insert the levelUp function here function levelUp(uint _zombieId) external payment function changeName(uint _zombieId, string _newName) external upperLevel(2, ieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) function getZombiesByOwner(address _owner) external return(uint[]) return result; & amp; amp; gt; & amp; amp; gt; & amp; amp; gt; & amp; amp; gt; pragma solidity ^0. 4. 19; import "./zombiefeeding. sol"; contract ZombieHelper is ZombieFeeding = _level); _; & amp; amp; gt; function changeName(uint _zombieId, string _newName) external upperLevel(2, _zombieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) // function get ZombiesByOwner(address _owner) external view return(uint[]) & amp; amp; gt; return result; & amp; amp; gt; & amp; amp; & amp; amp; gt; & amp; amp; & amp; amp; gt; function changeName(uint _zombieId, string _newName) external upperLevel(2, _zombieId) function changeDna(uint _zombieId, uint _newDna) external upperLevel(20, _zombieId) function getZombiesByOwner(address _owner) external view return(uint[])< // Start here modifier aboveLevel(uint _level, uint _zombieId) < require(zombies[_zombieId].level >Function _Createrandomzombie (Character string _name) Public 

zombiefeeding.sol - v10

PRAGMA SOLIDITY ^0. 4. 19; Import "./zombiefactory. Sol"; Contract Kitty Interface< 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 ); >The Zombiefeding contract is ZombieFactory.< KittyInterface kittyContract; function setKittyContractAddress(address _address) external onlyOwner < kittyContract = KittyInterface(_address); >Function feedandmultiply (UINT_ZOMBIEID, UINT_TARGETDNA, String_species) Public< _zombie.readyTime = uint32(now + cooldownTime); >_Createzombie ("Noname", newDNA); & amp; gt; feedonkitty (UINT_ZOMBIEID, UINT_KITTYID) Public< return (_zombie.readyTime // 1. Make this function internal function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal < require(msg.sender == zombieToOwner[_zombieId]); Zombie storage myZombie = zombies[_zombieId]; // 2. Add a check for `_isReady` here require(_isReady(myZombie)); _targetDna = _targetDna % dnaModulus; uint newDna = (myZombie.dna + _targetDna) / 2; if (keccak256(_species) == keccak256("kitty")) < newDna = newDna - newDna % 100 + 99; >Plagma Solidity ^0. 4. 19; // 1. Imports imported here ./OWNABLE. Sol "< uint kittyDna; (. kittyDna) = kittyContract.getKitty(_kittyId); feedAndMultiply(_zombieId, kittyDna, "kitty"); >> 

zombiefeeding.sol - v9

PRAGMA SOLIDITY ^0. 4. 19; Import "./zombiefactory. Sol"; Contract Kitty Interface< 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 ); >The Zombiefeding contract is ZombieFactory.< KittyInterface kittyContract; function setKittyContractAddress(address _address) external onlyOwner < kittyContract = KittyInterface(_address); >Function Createrandomzombie (String_name) Public< _zombie.readyTime = uint32(now + cooldownTime); >/** * @title Ownable * @dev Ownable contracts, including the owner's address and provide basic authority management. */ Contract Ownable< return(_zombie.readyTime 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; >/*** @dev produced when called from an account other than the owner. */ ONLYOWNER () Model< uint kittyDna; (. kittyDna) = kittyContract.getKitty(_kittyId); feedAndMultiply(_zombieId, kittyDna, "kitty"); >> 

zombiefactory.sol - v6

/*** @dev allows the current owner to transfer the contract control to a new owner. * @Param NEWOWNER The address of transferring ownership. */ Function Transferownership (Address New Owner) Public owner only.< event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; // 1. Define `cooldownTime` here uint cooldownTime = 1 days; struct Zombie < string name; uint dna; uint32 level; uint32 readyTime; >PRAGMA SOLIDITY ^0. 4. 19; IMPORT "./zombiefactory. Sol"; Contract KittyInterface< // 2. Update the following line: uint _dna, 1, uint32(now + cooldownTime))) - 1; // Note: The uint32(. ) is necessary because now returns a uint256 by default. So we need to explicitly convert it to a uint32. zombieToOwner[id] = msg.sender; ownerZombieCount[msg.sender]++; NewZombie(id, _name, _dna); >Contract Zombiefeeding is ZombieFactory< uint rand = uint(keccak256(_str)); return rand % dnaModulus; >Function feedandmultiply (UINT_ZOMBIEID, UINT_TARGETDNA, String_species) Public< require(ownerZombieCount[msg.sender] == 0); uint randDna = _generateRandomDna(_name); randDna = randDna - randDna % 100; _createZombie(_name, randDna); >> 
PRAGMA SOLIDITY ^0. 4. 19; Import "./zombiefactory. Sol"; Contract Kitty Interface< 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 ); >The Zombiefeding contract is ZombieFactory.< KittyInterface kittyContract; // Modify this function: function setKittyContractAddress(address _address) external onlyOwner < kittyContract = KittyInterface(_address); >Contract Zombiefeeding is ZombieFactory< 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; >/*** @dev produced when called from an account other than the owner. */ ONLYOWNER () Model< uint kittyDna; (. kittyDna) = kittyContract.getKitty(_kittyId); feedAndMultiply(_zombieId, kittyDna, "kitty"); >> 

zombiefactory.sol - v5

PRAGMA SOLIDITY ^0. 4. 19; IMPORT "./zombiefactory. Sol"; Contract KittyInterface< event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie < string name; uint dna; >PRAGMA SOLIDITY ^0. 4. 19; IMPORT "./zombiefactory. Sol"; Contract KittyInterface< uint _dna)) - 1; zombieToOwner[id] = msg.sender; ownerZombieCount[msg.sender]++; NewZombie(id, _name, _dna); >Contract Zombiefeeding is ZombieFactory< uint rand = uint(keccak256(_str)); return rand % dnaModulus; >Function feedandmultiply (UINT_ZOMBIEID, UINT_TARGETDNA, String_species) Public< require(ownerZombieCount[msg.sender] == 0); uint randDna = _generateRandomDna(_name); randDna = randDna - randDna % 100; _createZombie(_name, randDna); >> 

ownable.sol - v1

/** * @title ownable * @dev Ownable contracts include the owner's address and provide basic authority management functions. */ Contract Ownable< address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ function Ownable() public < owner = msg.sender; >/*** @dev produced when called from an account other than the owner. */ ONLYOWNER () Model< require(msg.sender == owner); _; >/*** @dev allows the current owner to transfer the contract control to a new owner. * @Param NEWOWNER The address of transferring ownership. */ Function Transferownership (Address New Owner) Public owner only.< require(newOwner != address(0)); OwnershipTransferred(owner, newOwner); owner = newOwner; >> 

zombiefeeding.sol - v7

PRAGMA SOLIDITY ^0. 4. 19; Import "./zombiefactory. Sol"; Contract Kitty Interface< 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 ); >The Zombiefeding contract is ZombieFactory.< // 1. Remove this: // 2. Change this to just a declaration: KittyInterface kittyContract; // 3. Add setKittyContractAddress method here function setKittyContractAddress (address _address) external < kittyContract = KittyInterface(_address); >Contract Zombiefeeding is ZombieFactory< 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; >/*** @dev produced when called from an account other than the owner. */ ONLYOWNER () Model< uint kittyDna; (. kittyDna) = kittyContract.getKitty(_kittyId); feedAndMultiply(_zombieId, kittyDna, "kitty"); >> 

zombiefeeding.sol - v6

PRAGMA SOLIDITY ^0. 4. 19; Import "./zombiefactory. Sol"; Contract Kitty Interface< 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 ); >The Zombiefeding contract is ZombieFactory.< address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; KittyInterface kittyContract = KittyInterface(ckAddress); // Modify function definition here: 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; // Explanation: Assume newDna is 334455. Then newDna % 100 is 55, so newDna - newDna % 100 is 334400. Finally add 99 to get 334499. >/*** @dev produced when called from an account other than the owner. */ ONLYOWNER () Model< uint kittyDna; (. kittyDna) = kittyContract.getKitty(_kittyId); // And modify function call here: feedAndMultiply(_zombieId, kittyDna, "kitty"); >> 

zombiefeeding.sol - v5

PRAGMA SOLIDITY ^0. 4. 19; Import "./zombiefactory. Sol"; Contract Kitty Interface< 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 ); >The Zombiefeding contract 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); >contract< uint kittyDna; // declaration (. kittyDna) = kittyContract.getKitty(_kittyId); //call the kittyContract.getKitty function with _kittyId and store genes in kittyDna feedAndMultiply(_zombieId, kittyDna); // passes on zombieId and kittyDna with function call >> 

zombiefeeding.sol - v4

PRAGMA SOLIDITY ^0. 4. 19; Import "./zombiefactory. Sol"; Contract Kitty Interface< 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 ); >The Zombiefeding contract is ZombieFactory.< address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; // Initialize kittyContract here using `ckAddress` from above 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); >> 

zombiefeeding.sol - v3

New zombie< 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 ); >The Zombiefeding contract 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); >> 

zombiefeeding.sol - v2

Zombie ID< 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; // dot notation to access properties of array _createZombie("NoName", newDna); >> 

zombiefeeding.sol - v1

Zombie ID< function feedAndMultiply(uint _zombieId, uint _targetDna) public < require(msg.sender == zombieToOwner[_zombieId]);// makes sure msg sender owns the zombie Zombie storage myZombie = zombies[_zombieId]; //gets the zombies DNA >> 

zombiefactory.sol - v4

name< event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie < string name; uint dna; >UINT< // made internal uint _dna)) - 1; zombieToOwner[id] = msg.sender; ownerZombieCount[msg.sender]++; NewZombie(id, _name, _dna); >Contract Zombiefeeding is ZombieFactory< uint rand = uint(keccak256(_str)); return rand % dnaModulus; >Function feedandmultiply (UINT_ZOMBIEID, UINT_TARGETDNA, String_species) Public< require(ownerZombieCount[msg.sender] == 0); uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); >> 
DNAC numbers UINT ^0.4DNAMODULUS; DNAC numbers structure  zombie line(name UINT, bottom zombie, name zombie); name UINT = 16; name public = 10 ** UINT; View address  bottom zombie; name zombie; > address[] name UINT; DNA (name => UINT) name busy; DNA (UINT => name) Zombie owner; message Sender(bottom message, name New zombie) name  name id = UINT._generatorandomdna(address(message, New zombie)) - 1; busy[id] = Return value.UINT; Zombie owner[Return value.UINT]++; line(id, message, New zombie); > message DNAMODULUS(bottom Create random zombies) name name Disclosure (name)  name message = name(// Limit the creation of zombies(Create random zombies)); randdNA message % public; > message name(bottom message) name  Function _Generatorandomdna (String _Str) Private View Returns (UINT)(Zombie owner[Return value.UINT] == 0); Comment out, no need to use UINT_DNA) -1; New Zombie (ID, _name, _DNA); & amp; gt; // This function sees some contract variables. However, it does not change name Seth Brown is a game expert who share knowledge about board games, card games, games tutorials, gameplay, and game strategies. A member of The Little Book of Mahjong and a member of Northern Berkshire Gaming Group. = DNAMODULUS(message); Sender(message, Seth Brown is a game expert who share knowledge about board games, card games, games tutorials, gameplay, and game strategies. A member of The Little Book of Mahjong and a member of Northern Berkshire Gaming Group.); > >

zombiecontract.sol - v2

name< event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie < string name; uint dna; >5 ships< uint _dna)) - 1; zomebieToOwner[id] = msg.sender; ownerZombieCount[msg.sender]++; NewZombie(id, _name, _dna); >Contract Zombiefeeding is ZombieFactory< uint rand = uint(keccak256(_str)); return rand % dnaModulus; >Function feedandmultiply (UINT_ZOMBIEID, UINT_TARGETDNA, String_species) Public< uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); >> 

zombiecontract.sol - v1

Cruing ship (3 holes)< //declare events up here event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; // sets the length of uint struct Zombie < string name; uint dna; >Submarine (3 holes)< //modified for privacy // zombies.push(Zombie(_name, _dna)); // =>Destroyer (2 holes)< //_str to generate a pseudo-random hexidecimal, typecast it as a uint uint rand = uint(keccak256(_str)); // DNA should only be 16 digits long so returns above % dnaModulus return rand % dnaModulus; >In the game table, two players have to face each other. The grids of each other are placed on back match, so that both players cannot see the "sea" and the opponent's ship.< uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); >> 

How to Play the Battleship Board Game

Before the start of the game, each opponent secretly place five ships on the marine grid (bottom of the board), lay the ship and fix it into the grid hole. Each ship must be placed horizontally or vertically, but cannot be placed diagonally. The ship can touch each other, but cannot be placed in the same space on the grid. You cannot change the position of the ship after the game starts.

Fir / Candice Madonna

Players alternately shoot shots (call the coordinates in the last) and aim to hit the opponent's enemy ship.

In your own turn, you call the rows and columns on the target grid. The opponent checks this coordinate on the grid of his own team, and reply verbally if there is no ship, if you can correctly guess the position of the ship, "hit".

Record a mistake with a white pin and a hit with red pin. As the game progresses, the red pin gradually determines the size and position of the enemy ship. < SPAN>, how do you play in battle ships? The game is very simple. Each player is placed on a plastic square with vertical and horizontal coordinates, but keeps the ship's position secret. The player alternately calls the opponent's square line and column coordinates to determine the trout with a ship.

The playfields that each player get has two grids at the top and bottom. The player places and hides on the lower grid. The upper square records a shot (indicating the coordinates) to the enemy, and records whether the shot has hit or off.

Setting up the Game

Each player receives a game board, five ships with different lengths (each ship has a hole for putting a "hit" pin), and a failure / failure marker (white and red pin).

5 ships

  • Media (5 holes)
  • Battleship (4 holes)
  • Cruing ship (3 holes)
  • Submarine (3 holes)
  • Destroyer (2 holes)

In the game table, two players have to face each other. The grids of each other are placed on back match, so that both players cannot see the "sea" and the opponent's ship.

Before the start of the game, each opponent secretly puts five ships on the marine grid (bottom of the board), lay the ship and fix it into the grid hole. Each ship must be placed horizontally or vertically, but cannot be placed diagonally. The ship can touch each other, but cannot be placed in the same space on the grid. You cannot change the position of the ship after the game starts.

Players alternately shoot shots (call the coordinates in the last) and aim to hit the opponent's enemy ship.

Basic Gameplay

Players alternately shoot shots (call the coordinates in the last) and aim to hit the opponent's enemy ship.

In your own turn, you call the rows and columns on the target grid. The opponent checks the coordinates on their own grid, and reply verbally if there is no ship, if you can correctly guess the position of the ship, "hit".

Record a mistake with a white pin and a hit with red pin. As the game progresses, the red pin gradually determines the size and position of the enemy ship. So how do you play Battle Ship? The game is very simple. Each player is placed on a plastic square with vertical and horizontal coordinates, but keeps the ship's position secret. The player alternately calls the opponent's square line and column coordinates to determine the trout with a ship.

The playfields that each player get has two grids at the top and bottom. The player places and hides on the lower grid. The upper square records a shot (indicating the coordinates) to the enemy, and records whether the shot has hit or off.

Each player receives a game board, five ships with different lengths (each ship has a hole for putting a "hit" pin), and a failure / failure marker (white and red pin).

5 ships

Advanced Gameplay

Media (5 holes)

  • Battleship (4 holes)
  • Cruing ship (3 holes)
  • Submarine (3 holes)
  • Destroyer (2 holes)
  • In the game table, two players have to face each other. The grids of each other are placed on back match, so that both players cannot see the "sea" and the opponent's ship.

Pencil and Paper Version

Before the start of the game, each opponent secretly place five ships on the marine grid (bottom of the board), lay the ship and fix it into the grid hole. Each ship must be placed horizontally or vertically, but cannot be placed diagonally. The ship can touch each other, but cannot be placed in the same space on the grid. You cannot change the position of the ship after the game starts.

Fir / Candice Madonna

Players alternately shoot shots (call the coordinates in the last) and aim to hit the opponent's enemy ship.

Players alternately shoot shots (call the coordinates in the last) and aim to hit the opponent's enemy ship.

avatar-logo

Elim Rim - Journalist, creative writer

Last modified 01.05.2025

This repository contains codes and notes from CryptoZombies (Solidity Tutorial & Etherium Blockchain Programming Course). CryptoZombies is a free, open. Lesson content for 100-zombies.lol Contribute to CryptozombiesHQ/cryptozombie-lessons development by creating an account on GitHub. This repository contains codes and notes from CryptoZombies (Solidity Tutorial & Etherium Blockchain Programming Course).

Play for real with EXCLUSIVE BONUSES
Play
enaccepted