# 编写有数据存储的智能合约

## 如何存储数据

EOS通过table的概念来存储智能合约运行中所需的数据。

数据储存在区块链上。

部署到区块链后，如果有数据存储，修改wasm编码后，会产生原始储存数据不可读的问题

## 我们看下官方例子tic\_tac\_toe

其中

hpp+cpp文件，主要实现了数据结构定义、业务逻辑、权限控制、数据存储

abi文件，定义了合约的types、structs、actions、tables和ricardian\_clauses

### hpp文件

```cpp
#include <eosiolib/eosio.hpp>

/**
 *  圆叉旗：
 *  在一个3*3的棋盘里，一人用x（先手host），一人用o（后手challenger），先连成三点一线的获胜。
 *  棋盘如下所示：
 *
 *  (0,0)坐标表示左上角
 *  (2,2)坐标表示右下角
 *
 *                 (0,2)
 *  (0,0) -  |  o  |  x            - = 空
 *        -  |  x  |  -            x = 先手
 *  (2,0) x  |  o  |  o            o = 后手
 * 
 *  为了让存储变得简单，在本例中我们用一个9个长度的数组，表示上面3*3的矩阵
 *  比如，上面的棋盘存储后就是，[0, 2, 1, 0, 1, 0, 1, 2, 2]
 *  
 *  另外，为了让智能合约变的简单，我们对每两对用户A和B，只允许开启两个旗局，一个是A为先手，一个是B为先手
 *  数据存储在先手账户中，并用后手作为key
 * 
 *  create创建游戏
 *  restart重开游戏
 *  close关闭游戏
 *  move下棋，先手后手交替进行
 */

namespace neo_tictactoe {
   //游戏开启账户，先手
   static const account_name games_account = N(games);
   //合约账户
   static const account_name code_account = N(tic.tac.toe);

   /**
    * @brief 用于保存棋盘信息的结构
    */
   static const uint32_t board_len = 9;
   struct game {
      game() { initialize_board(); }

      game(account_name challenger, account_name host):challenger(challenger), host(host), turn(host) {
         initialize_board();
      }

      account_name     challenger; //后手
      account_name     host; //先手
      account_name     turn; // 谁的回合
      account_name     winner = N(none); // 谁赢了 none/ draw/ host/ challenger
      uint8_t          board[board_len]; // 棋盘信息

      // 初始化游戏
      void initialize_board() {
         for (uint8_t i = 0; i < board_len ; i++) {
            board[i] = 0;
         }
      }

      // 重置游戏
      void reset_game() {
         initialize_board();
         turn = host;
         winner = N(none);
      }

      auto primary_key() const { return challenger; }

      EOSLIB_SERIALIZE( game, (challenger)(host)(turn)(winner)(board) )
   };

   /**
    * @brief 新建游戏
    */
   struct create {
      account_name   challenger;
      account_name   host;

      EOSLIB_SERIALIZE( create, (challenger)(host) )
   };

   /**
    * @brief 重置游戏
    */
   struct restart {
      account_name   challenger;
      account_name   host;
      account_name   by; // the account who wants to restart the game

      EOSLIB_SERIALIZE( restart, (challenger)(host)(by) )
   };

   /**
    * @brief 关闭游戏
    */
   struct close {
      account_name   challenger;
      account_name   host;

      EOSLIB_SERIALIZE( close, (challenger)(host) )
   };

   /**
    * @brief 下棋坐标
    */
   struct movement {
      uint32_t    row;
      uint32_t    column;

      EOSLIB_SERIALIZE( movement, (row)(column) )
   };

   /**
    * @brief 下棋
    */
   struct move {
      account_name   challenger;
      account_name   host;
      account_name   by; // the account who wants to make the move
      movement       mvt;

      EOSLIB_SERIALIZE( move, (challenger)(host)(by)(mvt) )
   };

   /**
    * @brief 新建表，保存账号和游戏信息
    */
   typedef eosio::multi_index< games_account, game> games;
}

```

### cpp文件

```cpp
#include "neo_tictactoe.hpp"

using namespace eosio;
namespace neo_tictactoe {
struct impl {
   /**
    * @brief Check if cell is empty
    * @param cell - value of the cell (should be either 0, 1, or 2)
    * @return true if cell is empty
    */
   bool is_empty_cell(const uint8_t& cell) {
      return cell == 0;
   }

   /**
    * @brief Check for valid movement
    * @detail Movement is considered valid if it is inside the board and done on empty cell
    * @param movement - the movement made by the player
    * @param game - the game on which the movement is being made
    * @return true if movement is valid
    */
   bool is_valid_movement(const movement& mvt, const game& game_for_movement) {
      uint32_t movement_location = mvt.row * 3 + mvt.column;
      bool is_valid = movement_location < board_len && is_empty_cell(game_for_movement.board[movement_location]);
      return is_valid;
   }


   /**
    * @brief Get winner of the game
    * @detail Winner of the game is the first player who made three consecutive aligned movement
    * @param game - the game which we want to determine the winner of
    * @return winner of the game (can be either none/ draw/ account name of host/ account name of challenger)
    */
   account_name get_winner(const game& current_game) {
      if((current_game.board[0] == current_game.board[4] && current_game.board[4] == current_game.board[8]) ||
         (current_game.board[1] == current_game.board[4] && current_game.board[4] == current_game.board[7]) ||
         (current_game.board[2] == current_game.board[4] && current_game.board[4] == current_game.board[6]) ||
         (current_game.board[3] == current_game.board[4] && current_game.board[4] == current_game.board[5])) {
         //  - | - | x    x | - | -    - | - | -    - | x | -
         //  - | x | -    - | x | -    x | x | x    - | x | -
         //  x | - | -    - | - | x    - | - | -    - | x | -
         if (current_game.board[4] == 1) {
            return current_game.host;
         } else if (current_game.board[4] == 2) {
            return current_game.challenger;
         }
      } else if ((current_game.board[0] == current_game.board[1] && current_game.board[1] == current_game.board[2]) ||
                 (current_game.board[0] == current_game.board[3] && current_game.board[3] == current_game.board[6])) {
         //  x | x | x       x | - | -
         //  - | - | -       x | - | -
         //  - | - | -       x | - | -
         if (current_game.board[0] == 1) {
            return current_game.host;
         } else if (current_game.board[0] == 2) {
            return current_game.challenger;
         }
      } else if ((current_game.board[2] == current_game.board[5] && current_game.board[5] == current_game.board[8]) ||
                 (current_game.board[6] == current_game.board[7] && current_game.board[7] == current_game.board[8])) {
         //  - | - | -       - | - | x
         //  - | - | -       - | - | x
         //  x | x | x       - | - | x
         if (current_game.board[8] == 1) {
            return current_game.host;
         } else if (current_game.board[8] == 2) {
            return current_game.challenger;
         }
      } else {
         bool is_board_full = true;
         for (uint8_t i = 0; i < board_len; i++) {
            if (is_empty_cell(current_game.board[i])) {
               is_board_full = false;
               break;
            }
         }
         if (is_board_full) {
            return N(draw);
         }
      }
      return N(none);
   }

   /**
    * @brief Apply create action
    * @param create - action to be applied
    */
   void on(const create& c) {
      require_auth(c.host);
      eosio_assert(c.challenger != c.host, "challenger shouldn't be the same as host");

      // Check if game already exists
      games existing_host_games(code_account, c.host);
      auto itr = existing_host_games.find( c.challenger );
      eosio_assert(itr == existing_host_games.end(), "game already exists");

      existing_host_games.emplace(c.host, [&]( auto& g ) {
         g.challenger = c.challenger;
         g.host = c.host;
         g.turn = c.host;
      });
   }

   /**
    * @brief Apply restart action
    * @param restart - action to be applied
    */
   void on(const restart& r) {
      require_auth(r.by);

      // Check if game exists
      games existing_host_games(code_account, r.host);
      auto itr = existing_host_games.find( r.challenger );
      eosio_assert(itr != existing_host_games.end(), "game doesn't exists");

      // Check if this game belongs to the action sender
      eosio_assert(r.by == itr->host || r.by == itr->challenger, "this is not your game!");

      // Reset game
      existing_host_games.modify(itr, itr->host, []( auto& g ) {
         g.reset_game();
      });
   }

   /**
    * @brief Apply close action
    * @param close - action to be applied
    */
   void on(const close& c) {
      require_auth(c.host);

      // Check if game exists
      games existing_host_games(code_account, c.host);
      auto itr = existing_host_games.find( c.challenger );
      eosio_assert(itr != existing_host_games.end(), "game doesn't exists");

      // Remove game
      existing_host_games.erase(itr);
   }

   /**
    * @brief Apply move action
    * @param move - action to be applied
    */
   void on(const move& m) {
      require_auth(m.by);

      // Check if game exists
      games existing_host_games(code_account, m.host);
      auto itr = existing_host_games.find( m.challenger );
      eosio_assert(itr != existing_host_games.end(), "game doesn't exists");

      // Check if this game hasn't ended yet
      eosio_assert(itr->winner == N(none), "the game has ended!");
      // Check if this game belongs to the action sender
      eosio_assert(m.by == itr->host || m.by == itr->challenger, "this is not your game!");
      // Check if this is the  action sender's turn
      eosio_assert(m.by == itr->turn, "it's not your turn yet!");


      // Check if user makes a valid movement
      eosio_assert(is_valid_movement(m.mvt, *itr), "not a valid movement!");

      // Fill the cell, 1 for host, 2 for challenger
      const auto cell_value = itr->turn == itr->host ? 1 : 2;
      const auto turn = itr->turn == itr->host ? itr->challenger : itr->host;
      existing_host_games.modify(itr, itr->host, [&]( auto& g ) {
         g.board[m.mvt.row * 3 + m.mvt.column] = cell_value;
         g.turn = turn;

         //check to see if we have a winner
         g.winner = get_winner(g);
      });
   }

   /// The apply method implements the dispatch of events to this contract
   void apply( uint64_t receiver, uint64_t code, uint64_t action ) {

      if (code == code_account) {
         if (action == N(create)) {
            impl::on(eosio::unpack_action_data<tic_tac_toe::create>());
         } else if (action == N(restart)) {
            impl::on(eosio::unpack_action_data<tic_tac_toe::restart>());
         } else if (action == N(close)) {
            impl::on(eosio::unpack_action_data<tic_tac_toe::close>());
         } else if (action == N(move)) {
            impl::on(eosio::unpack_action_data<tic_tac_toe::move>());
         }
      }
   }

};
}

/**
*  The apply() methods must have C calling convention so that the blockchain can lookup and
*  call these methods.
*/
extern "C" {

   using namespace neo_tictactoe;
   /// The apply method implements the dispatch of events to this contract
   void apply( uint64_t receiver, uint64_t code, uint64_t action ) {
      impl().apply(receiver, code, action);
   }

} // extern "C"

```

### abi文件

```javascript
{
  "types": [],
  "structs": [{
      "name": "game",
      "base": "",
      "fields": [
        {"name":"challenger", "type":"account_name"},
        {"name":"host", "type":"account_name"},
        {"name":"turn", "type":"account_name"},
        {"name":"winner", "type":"account_name"},
        {"name":"board", "type":"uint8[]"}
      ]
    },{
      "name": "create",
      "base": "",
      "fields": [
        {"name":"challenger", "type":"account_name"},
        {"name":"host", "type":"account_name"}
      ]
    },{
      "name": "restart",
      "base": "",
      "fields": [
        {"name":"challenger", "type":"account_name"},
        {"name":"host", "type":"account_name"},
        {"name":"by", "type":"account_name"}
      ]
    },{
      "name": "close",
      "base": "",
      "fields": [
        {"name":"challenger", "type":"account_name"},
        {"name":"host", "type":"account_name"}
      ]
    },{
      "name": "movement",
      "base": "",
      "fields": [
        {"name":"row", "type":"uint32"},
        {"name":"column", "type":"uint32"}
      ]
    },{
      "name": "move",
      "base": "",
      "fields": [
        {"name":"challenger", "type":"account_name"},
        {"name":"host", "type":"account_name"},
        {"name":"by", "type":"account_name"},
        {"name":"mvt", "type":"movement"}
      ]
    }
  ],
  "actions": [{
      "name": "create",
      "type": "create",
      "ricardian_contract": ""
    },{
      "name": "restart",
      "type": "restart",
      "ricardian_contract": ""
    },{
      "name": "close",
      "type": "close",
      "ricardian_contract": ""
    },{
      "name": "move",
      "type": "move",
      "ricardian_contract": ""
    }
  ],
  "tables": [{
        "name": "games",
        "type": "game",
        "index_type": "i64",
        "key_names" : ["challenger"],
        "key_types" : ["account_name"]
      }
  ],
  "ricardian_clauses": []
}

```

## 编译并加载合约

```
#编译
eosiocpp -o neo.tictactoe.wast neo.tictactoe.cpp
eosiocpp -g neo.tictactoe.abi neo.tictactoe.hpp
```

```bash
#开启区块
nodeos -e -p eosio --plugin eosio::wallet_api_plugin --plugin eosio::chain_api_plugin --plugin  eosio::account_history_api_plugin

#加载钱包

cleos wallet open
cleos wallet unlock
"PW5JAj9qw2tQsuVajLVXsiKUD7ULQnZ83qxpaB5YVFcs6qbhQ84J1"

cleos wallet open -n hospital2
cleos wallet unlock -n hospital2
"PW5JxnbBTTbTQKUHHNvYojU1xrcjxQduz2wU6gfMEgiZjwT5S6xJ6"

cleos wallet list

#创建用户
cleos create account eosio tictactoe1 EOS8K9wqRtD8cCuHvbdzcquEhYHnSaFvyQSWm8kVxYFHavraNeue2 EOS8K9wqRtD8cCuHvbdzcquEhYHnSaFvyQSWm8kVxYFHavraNeue2

#加载合约
cleos set contract tictactoe1  neo.tictactoe -p tictactoe1
```

## 执行合约

```bash
cleos push action tictactoe1 create '{"challenger":"patient2", "host":"patient1"}' -p patient1

cleos push action tictactoe1 move '{"challenger":"patient2", "host":"patient1", "by":"patient1", "mvt":{"row":0, "column":0} }' -p patient1

cleos push action tictactoe1 move '{"challenger":"patient2", "host":"patient1", "by":"patient2", "mvt":{"row":0, "column":1} }' -p patient2

cleos push action tictactoe1 move '{"challenger":"patient2", "host":"patient1", "by":"patient1", "mvt":{"row":1, "column":1} }' -p patient1

cleos push action tictactoe1 move '{"challenger":"patient2", "host":"patient1", "by":"patient2", "mvt":{"row":2, "column":2} }' -p patient2

cleos push action tictactoe1 move '{"challenger":"patient2", "host":"patient1", "by":"patient1", "mvt":{"row":0, "column":2} }' -p patient1

cleos push action tictactoe1 move '{"challenger":"patient2", "host":"patient1", "by":"patient2", "mvt":{"row":1, "column":0} }' -p patient2

cleos push action tictactoe1 move '{"challenger":"patient2", "host":"patient1", "by":"patient1", "mvt":{"row":2, "column":0} }' -p patient1

cleos push action tictactoe1 move '{"challenger":"patient2", "host":"patient1", "by":"patient2", "mvt":{"row":2, "column":0} }' -p patient2
```

## 查看数据

```
cleos get table tictactoe1 patient1 games
{
  "rows": [{
      "challenger": "patient2",
      "host": "patient1",
      "turn": "patient2",
      "winner": "patient1",
      "board": [
        1,
        2,
        1,
        2,
        1,
        0,
        1,
        0,
        2
      ]
    }
  ],
  "more": false
}

```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://neochain.gitbook.io/project/eos/bian-xie-you-shu-ju-cun-chu-de-zhi-neng-he-yue.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
