# Perform basic operations · Tic Tac Toe on Chromia with Rell and Unity · Learn Chromia

> This part of the tutorial covers the core operations for running a Tic Tac Toe game on the Chromia blockchain: creating a game, starting new games, and…

Source: https://chromia.com/learn/tic-tac-toe/module-one/data-modeling/basic-operations/

Tic Tac Toe on Chromia with Rell and UnityLesson 6 of 20

1. [01Tic Tac Toe on Chromia with Rell and Unity](https://chromia.com/learn/tic-tac-toe/introduction/)
2. [02Set up your project](https://chromia.com/learn/tic-tac-toe/setup/)

Module 1: A Rell backend with FT4 accounts

1. [03Overview](https://chromia.com/learn/tic-tac-toe/module-one/)
2. [04Part 1: Database design, operations and queries](https://chromia.com/learn/tic-tac-toe/module-one/data-modeling/)
3. [05Design the data model](https://chromia.com/learn/tic-tac-toe/module-one/data-modeling/tables/)
4. [06Perform basic operations](https://chromia.com/learn/tic-tac-toe/module-one/data-modeling/basic-operations/)
5. [07Write basic queries](https://chromia.com/learn/tic-tac-toe/module-one/data-modeling/write-queries/)
6. [08Part 2: Set up accounts](https://chromia.com/learn/tic-tac-toe/module-one/create-accounts/)
7. [09Configure FT4 accounts](https://chromia.com/learn/tic-tac-toe/module-one/create-accounts/install-configure-ft4/)
8. [10Authentication with FT4 accounts](https://chromia.com/learn/tic-tac-toe/module-one/create-accounts/authentication/)
9. [11Update tests](https://chromia.com/learn/tic-tac-toe/module-one/create-accounts/update-tests/)
10. [12Part 3: How the dapp project is organized](https://chromia.com/learn/tic-tac-toe/module-one/project-structure/)
11. [13Work with Rell modules](https://chromia.com/learn/tic-tac-toe/module-one/project-structure/modules/)
12. [14Add modules to your dapp](https://chromia.com/learn/tic-tac-toe/module-one/project-structure/incorporate-modules/)

Module 2: The Unity project

1. [15Overview](https://chromia.com/learn/tic-tac-toe/module-two/)
2. [16Set up the project](https://chromia.com/learn/tic-tac-toe/module-two/setup/)
3. [17Project scaffold](https://chromia.com/learn/tic-tac-toe/module-two/scaffold/)
4. [18Link the client to the blockchain](https://chromia.com/learn/tic-tac-toe/module-two/connecting-the-client/)
5. [19Summary and manual testing](https://chromia.com/learn/tic-tac-toe/module-two/summary-and-tests/)

1. [20What next?](https://chromia.com/learn/tic-tac-toe/what-next/)

[Learn](https://chromia.com/learn/)[Tic Tac Toe on Chromia with Rell and Unity](https://chromia.com/learn/tic-tac-toe/)

06 / 20Module 1: A Rell backend with FT4 accounts

# Perform basic operations

This part of the tutorial covers the core operations for running a Tic Tac Toe game on the Chromia blockchain: creating a game, starting new games, and placing moves on the board. When you finish, you will be able to implement and test these basic operations in your decentralized game.

## Add data through transactions

On Chromia, you write data to the blockchain by submitting transactions. Each transaction contains one or more database operations. The operations below handle creating games, joining them, and making moves.

### Create a game

src/main/operations.rell

```
operation create_game() {
    val account = auth.authenticate();
    functions.create_game(account);
}
```

`create_game` creates a new instance of the predefined `game` entity. For that instance, it fills in starting values such as `created_by` and `created_at`, and marks every board slot (`slot_1` to `slot_9`) as `slot_states.free`.

### Join a game

src/main/operations.rell

```
operation join_game(entities.game) {
    val account = auth.authenticate();
    functions.join_game(game, account);
}
```

`join_game` lets a second player enter an existing game. It authenticates the player's account, then writes it to the `player2_account_rowid` field of that `game` instance. Unauthorized players cannot join.

### Make a move

src/main/operations.rell

```
operation make_move(entities.game, slot: integer) {
    val account = auth.authenticate();
    functions.make_move(game, slot, account);
}
```

`make_move` records a player's mark by updating the matching slot of the `game` instance. Before it saves the move, it checks that:

* The slot number falls between 1 and 9.
* The player is authorized and the turn belongs to them.
* The game is active.
* Nobody has already taken the chosen slot.

Once these checks pass, `make_move` stores the move in the game state, then determines whether the game is won, drawn, or continues with the other player's turn.

## Test the operations

Unit tests confirm that your game logic behaves the way you intend. Write and run them to verify each operation.

### Set up the test module

1. Go to the test folder in your project directory. Create it if it is missing.
2. Inside the test folder, add a file named `module.rell`. Your test cases go here.
3. Put this header at the top of `module.rell`:

src/test/tic\_tac\_toe/module.rell

```
@test module;

import tic_tac_toe.{ game, game_status, slot_states };
import tic_tac_toe.external.{ create_game, get_games_by_status, join_game, make_move, get_game_by_id };

import lib.ft4.accounts.{ account, auth_descriptor };
import lib.ft4.core.accounts.strategies.open.{ ras_open };
import lib.ft4.test.core. { create_auth_descriptor, ft_auth_operation_for };
import lib.ft4.external.accounts.strategies.{ register_account };
```

* `@test module;` marks the module as one that holds test cases.
* The `import` statements pull in what you need from the `tic_tac_toe` and `lib.ft4` libraries:
  + **Game management** functions from the `tic_tac_toe.external` module: `create_game`, `get_games_by_status`, `join_game`, `make_move`, and `get_game_by_id`.
  + **Account and authentication utilities** (`account`, `auth_descriptor`, `register_account`) for creating and managing the player accounts your tests use.
  + **Test utilities** (`create_auth_descriptor`, `ft_auth_operation_for`) for test-specific setup and authentication.

These imports give `module.rell` access to the main game functions and player management operations, so you can start writing test cases.

### Write test cases

The following tests check that the basic game operations behave correctly.

#### Create account test

This test creates user accounts with the `create_test_user_data` function and confirms they were added by checking how many accounts exist.

src/test/tic\_tac\_toe/module.rell

```
function test_create_accounts() {
    create_test_user_data(rell.test.keypairs.bob);
    create_test_user_data(rell.test.keypairs.eve);

    assert_equals(get_all_account().size(), 2);
}
```

What the test does:

##### Test: `test_create_accounts`

* **Initialize accounts:**
  + Call `create_test_user_data` for two users, `bob_kp` (Bob) and `eve_kp` (Eve), passing each user's keypair from `rell.test.keypairs`.
* **Verify account creation:**
  + Get the total account count with `get_all_account().size()`.
  + Check that the count is exactly two with `assert_equals(get_all_account().size(), 2)`.

#### Create game test

These tests confirm that a player can create a game and that the creation rules are enforced.

rell

```
function test_create_game() {
    val (bob_kp, bob_account) = create_test_user_data(rell.test.keypairs.bob);

    val last_block_time = rell.test.last_block_time;
    create_test_game(bob_kp);

    val all_created_games = get_games_by_status(game_status.created);

    val all_created_games_expected = [
        (
        id = 6,
        created_by = bob_account.id,
        created_at = last_block_time,
        player1_account_id = bob_account.id,
        player2_account_id = null,
        last_step_account_id = bob_account.id,
        last_step_at = last_block_time,
        winner = null,
        slot_1 = slot_states.free,
        slot_2 = slot_states.free,
        slot_3 = slot_states.free,
        slot_4 = slot_states.free,
        slot_5 = slot_states.free,
        slot_6 = slot_states.free,
        slot_7 = slot_states.free,
        slot_8 = slot_states.free,
        slot_9 = slot_states.free,
        status = game_status.created
    )
    ];

    assert_equals(all_created_games.to_gtv_pretty(), all_created_games_expected.to_gtv_pretty());
}

function test_create_game_if_already_in_one_must_fail() {
    val (bob_kp, _) = create_test_user_data(rell.test.keypairs.bob);

    create_test_game(bob_kp);

    rell.test.tx()
        .op(ft_auth_operation_for(bob_kp.pub))
        .op(create_game())
        .sign(bob_kp)
        .run_must_fail(
            "Player %s are already participating in an ongoing game and cannot join another.".format(bob_kp.pub.hash())
        );
}
```

What the tests do:

##### Test: `test_create_game`

* **Initialize test user data**:
  + Create a keypair and account data for Bob with `create_test_user_data`.
* **Set up and record initial game state**:
  + Save the current blockchain timestamp as `last_block_time`.
  + Create a game owned by Bob by calling `create_test_game(bob_kp)`.
* **Verify created games**:
  + Fetch games whose status is `created` through `get_games_by_status`.
  + Build the expected state in `all_created_games_expected`: Bob's ID in both the `created_by` and `player1_account_id` fields, and every slot set to `free`.
  + Compare actual and expected state with `assert_equals` to confirm the new game was set up correctly.

##### Test: `test_create_game_if_already_in_one_must_fail`

* **Set up keypair and create initial game**:
  + Prepare Bob's keypair.
  + Create a first game with `create_test_game(bob_kp)`.
* **Attempt to create a second game and expect failure**:
  + Call `create_game` for Bob again while his first game is still ongoing.
  + Wrap the call in `run_must_fail` so the test passes only if the transaction fails, and check for an error message stating that a player already in a game cannot join another one.

#### Join game test

These tests confirm that a user can join a game and that an active game accepts no players beyond the second one. They create a game, add a second player, and check the resulting state, then verify that joining an already active game fails.

src/test/tic\_tac\_toe/module.rell

```
function test_join_game() {
    val (bob_kp, bob_account) = create_test_user_data(rell.test.keypairs.bob);
    val (eve_kp, eve_account) = create_test_user_data(rell.test.keypairs.eve);

    val game = create_test_game(bob_kp);

    val all_created_games = get_games_by_status(game_status.created);

    join_test_game(game, eve_kp);

    val game_data = get_game_by_id(game.rowid.to_integer());

    val game_data_expected = (
        id = game.rowid,
        created_by = bob_account.id,
        created_at = all_created_games[0].created_at,
        player1_account_id = bob_account.id,
        player2_account_id = eve_account.id,
        last_step_account_id = bob_account.id,
        last_step_at = all_created_games[0].last_step_at,
        winner = null,
        slot_1 = slot_states.free,
        slot_2 = slot_states.free,
        slot_3 = slot_states.free,
        slot_4 = slot_states.free,
        slot_5 = slot_states.free,
        slot_6 = slot_states.free,
        slot_7 = slot_states.free,
        slot_8 = slot_states.free,
        slot_9 = slot_states.free,
        status = game_status.active
    );

    assert_equals(game_data.to_gtv_pretty(), game_data_expected.to_gtv_pretty());
}

function test_join_active_game_must_fail() {
    val (bob_kp, _) = create_test_user_data(rell.test.keypairs.bob);
    val (eve_kp, _) = create_test_user_data(rell.test.keypairs.eve);
    val (alice_kp, _) = create_test_user_data(rell.test.keypairs.alice);

    val game = create_test_game(bob_kp);

    join_test_game(game, eve_kp);
    join_test_game_must_fail(game, alice_kp, "The game has already started.");
}
```

What the tests do:

##### Test: `test_join_game`

* **Initialize accounts:**
  + Call `create_test_user_data` for `bob_kp` (Bob) and `eve_kp` (Eve) with their keypairs from `rell.test.keypairs`.
* **Create and retrieve game:**
  + Have Bob create a game through `create_test_game(bob_kp)`.
  + Look up the game with `get_games_by_status(game_status.created)`, which confirms it is in the "created" state.
* **Join game:**
  + Add Eve to the game with `join_test_game(game, eve_kp)`.
* **Verify game state:**
  + Load the game data with `get_game_by_id(game.rowid.to_integer())`.
  + Build the expected state, `game_data_expected`, which requires:
    - Bob recorded as creator (`created_by = bob_account.id`)
    - Bob and Eve both assigned as players
    - A status of `game_status.active`
  + Check that the two match with `assert_equals(game_data.to_gtv_pretty(), game_data_expected.to_gtv_pretty())`.

##### Test: `test_join_active_game_must_fail`

* **Initialize accounts:**
  + Create accounts for three users with `create_test_user_data`: `bob_kp` (Bob), `eve_kp` (Eve), and `alice_kp` (Alice).
* **Create and join game:**
  + Bob creates a game through `create_test_game(bob_kp)`.
  + Eve joins it through `join_test_game(game, eve_kp)`.
* **Attempt to join active game:**
  + Call `join_test_game_must_fail(game, alice_kp, "The game has already started.")` to confirm that Alice cannot join the active game, since a game allows only two players.

#### Make move test

These tests confirm that players can place moves and that the game rules are enforced.

rell

```
function test_make_move() {
    val (bob_kp, bob_account) = create_test_user_data(rell.test.keypairs.bob);
    val (eve_kp, eve_account) = create_test_user_data(rell.test.keypairs.eve);

    val game_created_time = rell.test.last_block_time;
    val game = create_test_game(bob_kp);

    join_test_game(game, eve_kp);

    val last_move_time = rell.test.last_block_time;

    make_test_move(game, 1, eve_kp);

    val game_data = get_game_by_id(game.rowid.to_integer());

    val game_data_expected = (
        id = game.rowid,
        created_by = bob_account.id,
        created_at = game_created_time,
        player1_account_id = bob_account.id,
        player2_account_id = eve_account.id,
        last_step_account_id = eve_account.id,
        last_step_at = last_move_time,
        winner = null,
        slot_1 = slot_states.one,
        slot_2 = slot_states.free,
        slot_3 = slot_states.free,
        slot_4 = slot_states.free,
        slot_5 = slot_states.free,
        slot_6 = slot_states.free,
        slot_7 = slot_states.free,
        slot_8 = slot_states.free,
        slot_9 = slot_states.free,
        status = game_status.active
    );

    assert_equals(game_data.to_gtv_pretty(), game_data_expected.to_gtv_pretty());
}

function test_move_on_wrong_turn_must_fail() {
    val (bob_kp, _) = create_test_user_data(rell.test.keypairs.bob);
    val (eve_kp, _) = create_test_user_data(rell.test.keypairs.eve);

    val game = create_test_game(bob_kp);

    join_test_game(game, eve_kp);

    make_test_move_must_fail(game, 1, bob_kp, "Action denied: incorrect player attempting to move.");
}

function test_move_with_wrong_slot_must_fail() {
    val (bob_kp, _) = create_test_user_data(rell.test.keypairs.bob);
    val (eve_kp, _) = create_test_user_data(rell.test.keypairs.eve);

    val game = create_test_game(bob_kp);

    join_test_game(game, eve_kp);

    make_test_move_must_fail(game, 10, eve_kp, "Wrong slot.");
}

function test_move_by_non_participant_slot_must_fail() {
    val (bob_kp, _) = create_test_user_data(rell.test.keypairs.bob);
    val (eve_kp, _) = create_test_user_data(rell.test.keypairs.eve);
    val (alice_kp, _) = create_test_user_data(rell.test.keypairs.alice);

    val game = create_test_game(bob_kp);

    join_test_game(game, eve_kp);

    make_test_move_must_fail(game, 1, alice_kp, "Player is not a participant in this game.");
}

function test_move_on_taken_slot_must_fail() {
    val (bob_kp, _) = create_test_user_data(rell.test.keypairs.bob);
    val (eve_kp, _) = create_test_user_data(rell.test.keypairs.eve);

    val game = create_test_game(bob_kp);

    join_test_game(game, eve_kp);
    make_test_move(game, 1, eve_kp);
    make_test_move_must_fail(game, 1, bob_kp, "Slot already taken.");
}

function test_move_in_non_active_game_must_fail() {
    val (bob_kp, _) = create_test_user_data(rell.test.keypairs.bob);

    val game = create_test_game(bob_kp);

    make_test_move_must_fail(game, 1, bob_kp, "Game is not active.");
}
```

What the tests do:

##### Test: `test_make_move`

* **Initialize test user data**:
  + Create keypairs and accounts for Bob and Eve with `create_test_user_data`.
* **Create and join game**:
  + Bob creates a game through `create_test_game(bob_kp)`.
  + Eve joins through `join_test_game(game, eve_kp)`.
* **Make a move**:
  + Eve takes slot 1 with `make_test_move(game, 1, eve_kp)`.
* **Verify game state after move**:
  + Load the updated game with `get_game_by_id`.
  + Build the expected game data, including the updated player details and slot states.
  + Compare actual and expected data with `assert_equals` to confirm the move was stored correctly.

##### Test: `test_move_on_wrong_turn_must_fail`

* **Set up keypairs**:
  + Prepare keypairs for Bob and Eve.
* **Create game and join**:
  + Bob creates a game and Eve joins it.
* **Attempt move on wrong turn**:
  + Bob tries to move during Eve's turn through `make_test_move_must_fail`. The move must fail with a matching error message.

##### Test: `test_move_with_wrong_slot_must_fail`

* **Set up keypairs**:
  + Prepare keypairs for Bob and Eve.
* **Create game and join**:
  + Bob creates a game and Eve joins it.
* **Attempt move with invalid slot**:
  + Eve tries slot 10, which does not exist. The move must fail with the message "Wrong slot."

##### Test: `test_move_by_non_participant_slot_must_fail`

* **Set up keypairs**:
  + Prepare keypairs for Bob, Eve, and Alice.
* **Create game and join**:
  + Bob creates a game and Eve joins it.
* **Attempt move by a non-participant**:
  + Alice tries to move. The move must fail with the message "Player is not a participant in this game."

##### Test: `test_move_on_taken_slot_must_fail`

* **Set up keypairs**:
  + Prepare keypairs for Bob and Eve.
* **Create game and join**:
  + Bob creates a game and Eve joins it.
* **Make first move**:
  + Eve takes slot 1.
* **Attempt move in taken slot**:
  + Bob tries the same slot. The move must fail with the message "Slot already taken."

##### Test: `test_move_in_non_active_game_must_fail`

* **Set up keypairs**:
  + Prepare a keypair for Bob.
* **Create game**:
  + Bob creates a game.
* **Attempt to move in a non-active game**:
  + Bob tries to move. The move must fail with the message "Game is not active."

#### Create play full game test

rell

```
function test_play_full_game_first_player_win() {
    val (bob_kp, bob_account) = create_test_user_data(rell.test.keypairs.bob);
    val (eve_kp, eve_account) = create_test_user_data(rell.test.keypairs.eve);

    val game_created_time = rell.test.last_block_time;
    val game = create_test_game(bob_kp);

    join_test_game(game, eve_kp);

    make_test_move(game, 1, eve_kp);
    make_test_move(game, 4, bob_kp);
    make_test_move(game, 3, eve_kp);
    make_test_move(game, 5, bob_kp);
    make_test_move(game, 9, eve_kp);
    val last_move_time = rell.test.last_block_time;
    make_test_move(game, 6, bob_kp);

    val game_data = get_game_by_id(game.rowid.to_integer());

    val game_data_expected = (
        id = game.rowid,
        created_by = bob_account.id,
        created_at = game_created_time,
        player1_account_id = bob_account.id,
        player2_account_id = eve_account.id,
        last_step_account_id = bob_account.id,
        last_step_at = last_move_time,
        winner = bob_account.id,
        slot_1 = slot_states.one,
        slot_2 = slot_states.free,
        slot_3 = slot_states.one,
        slot_4 = slot_states.zero,
        slot_5 = slot_states.zero,
        slot_6 = slot_states.zero,
        slot_7 = slot_states.free,
        slot_8 = slot_states.free,
        slot_9 = slot_states.one,
        status = game_status.winner
    );

    assert_equals(game_data.to_gtv_pretty(), game_data_expected.to_gtv_pretty());
}

function test_play_full_game_second_player_win() {
    val (bob_kp, bob_account) = create_test_user_data(rell.test.keypairs.bob);
    val (eve_kp, eve_account) = create_test_user_data(rell.test.keypairs.eve);

    val game_created_time = rell.test.last_block_time;
    val game = create_test_game(bob_kp);

    join_test_game(game, eve_kp);

    make_test_move(game, 1, eve_kp);
    make_test_move(game, 2, bob_kp);
    make_test_move(game, 4, eve_kp);
    make_test_move(game, 3, bob_kp);
    val last_move_time = rell.test.last_block_time;
    make_test_move(game, 7, eve_kp);

    val game_data = get_game_by_id(game.rowid.to_integer());

    val game_data_expected = (
        id = game.rowid,
        created_by = bob_account.id,
        created_at = game_created_time,
        player1_account_id = bob_account.id,
        player2_account_id = eve_account.id,
        last_step_account_id = eve_account.id,
        last_step_at = last_move_time,
        winner = eve_account.id,
        slot_1 = slot_states.one,
        slot_2 = slot_states.zero,
        slot_3 = slot_states.zero,
        slot_4 = slot_states.one,
        slot_5 = slot_states.free,
        slot_6 = slot_states.free,
        slot_7 = slot_states.one,
        slot_8 = slot_states.free,
        slot_9 = slot_states.free,
        status = game_status.winner
    );

    assert_equals(game_data.to_gtv_pretty(), game_data_expected.to_gtv_pretty());
}

function test_play_full_game_draw() {
    val (bob_kp, bob_account) = create_test_user_data(rell.test.keypairs.bob);
    val (eve_kp, eve_account) = create_test_user_data(rell.test.keypairs.eve);

    val game_created_time = rell.test.last_block_time;
    val game = create_test_game(bob_kp);

    join_test_game(game, eve_kp);

    make_test_move(game, 2, eve_kp);
    make_test_move(game, 1, bob_kp);
    make_test_move(game, 4, eve_kp);
    make_test_move(game, 3, bob_kp);
    make_test_move(game, 5, eve_kp);
    make_test_move(game, 6, bob_kp);
    make_test_move(game, 7, eve_kp);
    make_test_move(game, 8, bob_kp);
    val last_move_time = rell.test.last_block_time;
    make_test_move(game, 9, eve_kp);

    val game_data = get_game_by_id(game.rowid.to_integer());

    val game_data_expected = (
        id = game.rowid,
        created_by = bob_account.id,
        created_at = game_created_time,
        player1_account_id = bob_account.id,
        player2_account_id = eve_account.id,
        last_step_account_id = eve_account.id,
        last_step_at = last_move_time,
        winner = null,
        slot_1 = slot_states.zero,
        slot_2 = slot_states.one,
        slot_3 = slot_states.zero,
        slot_4 = slot_states.one,
        slot_5 = slot_states.one,
        slot_6 = slot_states.zero,
        slot_7 = slot_states.one,
        slot_8 = slot_states.zero,
        slot_9 = slot_states.one,
        status = game_status.draw
    );

    assert_equals(game_data.to_gtv_pretty(), game_data_expected.to_gtv_pretty());
}

function test_create_new_game_after_completion() {
    val (bob_kp, _) = create_test_user_data(rell.test.keypairs.bob);
    val (eve_kp, _) = create_test_user_data(rell.test.keypairs.eve);

    val game = create_test_game(bob_kp);

    join_test_game(game, eve_kp);

    make_test_move(game, 1, eve_kp);
    make_test_move(game, 4, bob_kp);
    make_test_move(game, 3, eve_kp);
    make_test_move(game, 5, bob_kp);
    make_test_move(game, 9, eve_kp);
    make_test_move(game, 6, bob_kp);

    create_test_game(bob_kp);
    val all_created_games = get_games_by_status(game_status.created);

    assert_equals(all_created_games.size(), 1);
}
```

What the tests do:

##### Test: `test_play_full_game_first_player_win`

* **Initialize keypairs:**
  + Create the keypairs `bob_kp` and `eve_kp` so Bob and Eve can play.
* **Start a game:**
  + Bob creates the game through `create_test_game(bob_kp)`.
* **Join and play:**
  + Eve joins through `join_test_game(game, eve_kp)`.
  + A series of moves plays out the game until Bob wins on move 6.
* **Verify outcome:**
  + Load the game data and confirm that:
    - The winner is `bob_account.id`.
    - The board shows the expected winning pattern.
    - The status is `game_status.winner`.

##### Test: `test_play_full_game_second_player_win`

* **Initialize keypairs and start a game:**
  + Same setup as the previous test: create keypairs and have Bob create the game.
* **Simulate gameplay:**
  + A series of moves ends with Eve winning on move 7.
* **Verify outcome:**
  + Load the game data and confirm that:
    - The winner is `eve_account.id`.
    - The board shows the expected winning pattern.
    - The status is `game_status.winner`.

##### Test: `test_play_full_game_draw`

* **Initialize and start a game:**
  + Same setup as the tests above.
* **Simulate gameplay:**
  + A series of moves ends in a draw, with no winning line formed.
* **Verify outcome:**
  + Confirm that:
    - The winner is `null`, meaning the game was drawn.
    - Every slot is filled and no winning pattern exists.
    - The status is `game_status.draw`.

##### Test: `test_create_new_game_after_completion`

* **Initialize keypairs and start a game:**
  + Create keypairs and have Bob create a game.
* **Simulate gameplay and restart:**
  + A series of moves finishes the game with a win.
  + After the game ends, create a new game for Bob, then run the check below.
* **Verify outcome:**
  + Confirm that exactly one game has the status `game_status.created`.

### Run the tests

With the tests written, run them to check that your dapp works.

1. In a terminal, change to your project's root directory.
2. Execute the tests with this command:

shell

```nolines
chr test
```

The command runs the tests listed under `test: modules` in `chromia.yml`. When every test passes, the output confirms that your game behaves as expected.

You have now implemented and tested the core operations of a decentralized Tic Tac Toe game on the Chromia blockchain.

The next part of the tutorial covers more advanced features, including game state persistence and user input validation.

Written for this site from Chromia’s public course. Code samples are the course’s own.

[← Previous**Design the data model**](https://chromia.com/learn/tic-tac-toe/module-one/data-modeling/tables/)[Next →**Write basic queries**](https://chromia.com/learn/tic-tac-toe/module-one/data-modeling/write-queries/)

On this page

* [Add data through transactions](#add-data-through-transactions)
* [Test the operations](#test-the-operations)
