4. Test Contract
In this chapter, we'll use everything we've learned to run a full chess game scenario.
Here's what we'll do in our test:
- Spawn
white_pawn_1to (0,1) - Move
white_pawn_1to (0,3) - Move
black_pawn_2to (1,6) - Move
white_pawn_1to (0,4) - Move
black_pawn_2to (1,5) - Move
white_pawn_1to (1,5) - Capture
black_pawn_2
To place the pieces, use our initiate_system. For moving them, use the move_system. Remember to check if a piece can be captured when using move_system.
Before we get to the code, set up your integration test like this:
- Copy the test below and add it to your
src/tests.cairofile. - Make a
test.cairoin your src and updatelib.cairoby adding themod tests;line.
Full Code
#[cfg(test)]
mod tests {
use starknet::ContractAddress;
use dojo::test_utils::spawn_test_world;
use dojo_chess::components::{Game, game, GameTurn, game_turn, Square, square, PieceType};
use dojo_chess::systems::initiate_system;
use dojo_chess::systems::move_system;
use array::ArrayTrait;
use core::traits::Into;
use dojo::world::IWorldDispatcherTrait;
use core::array::SpanTrait;
#[test]
#[available_gas(3000000000000000)]
fn integration() {
let white = starknet::contract_address_const::<0x01>();
let black = starknet::contract_address_const::<0x02>();
// components
let mut components = array::ArrayTrait::new();
components.append(game::TEST_CLASS_HASH);
components.append(game_turn::TEST_CLASS_HASH);
components.append(square::TEST_CLASS_HASH);
//systems
let mut systems = array::ArrayTrait::new();
systems.append(initiate_system::TEST_CLASS_HASH);
systems.append(move_system::TEST_CLASS_HASH);
let world = spawn_test_world(components, systems);
// initiate
let mut calldata = array::ArrayTrait::<core::felt252>::new();
calldata.append(white.into());
calldata.append(black.into());
world.execute('initiate_system'.into(), calldata);
let game_id = pedersen(white.into(), black.into());
//White pawn is now in (0,1)
let a2 = get!(world, (game_id, 0, 1), (Square));
match a2.piece {
Option::Some(piece) => {
assert(piece == PieceType::WhitePawn, "should be White Pawn in (0,1)");
},
Option::None(_) => assert(false, 'should have piece in (0,1)),
};
//Black pawn is now in (1,6)
let b7 = get!(world, (game_id, 1, 6), (Square));
match b7.piece {
Option::Some(piece) => {
assert(piece == PieceType::BlackPawn, "should be Black Pawn in (1,6)");
},
Option::None(_) => assert(false, 'should have piece in (1,6)),
};
//Move White Pawn to (0,3)
let mut move_calldata = array::ArrayTrait::<core::felt252>::new();
move_calldata.append(0);
move_calldata.append(1);
move_calldata.append(0);
move_calldata.append(3);
move_calldata.append(white.into());
move_calldata.append(game_id);
world.execute('move_system'.into(), move_calldata);
//White pawn is now in (0,3)
let a4 = get!(world, (game_id, 0, 3), (Square));
match a4.piece {
Option::Some(piece) => {
assert(piece == PieceType::WhitePawn, "should be White Pawn in (0,3)");
},
Option::None(_) => assert(false, 'should have piece in (0,3)),
};
//Move black Pawn to (1,4)
let mut move_calldata = array::ArrayTrait::<core::felt252>::new();
move_calldata.append(1);
move_calldata.append(6);
move_calldata.append(1);
move_calldata.append(4);
move_calldata.append(black.into());
move_calldata.append(game_id);
world.execute('move_system'.into(), move_calldata);
//Black pawn is now in (1,4)
let b5 = get!(world, (game_id, 1, 4), (Square));
match b5.piece {
Option::Some(piece) => {
assert(piece == PieceType::BlackPawn, "should be Black Pawn in (1,4)");
},
Option::None(_) => assert(false, 'should have piece in (1,4)),
};
// Move White Pawn to (1,4)
// Capture black pawn
let mut move_calldata = array::ArrayTrait::<core::felt252>::new();
move_calldata.append(0);
move_calldata.append(3);
move_calldata.append(1);
move_calldata.append(4);
move_calldata.append(white.into());
move_calldata.append(game_id);
world.execute('move_system'.into(), move_calldata);
let b5 = get!(world, (game_id, 1, 4), (Square));
match b5.piece {
Option::Some(piece) => {
assert(piece == PieceType::WhitePawn, "should be WhitePawn in (1,4)");
},
Option::None(_) => assert(false, 'should have piece in (1,4)),
};
}
}
Diving into the Code
First, we'll set up the players and their colors.
let white = starknet::contract_address_const::<0x01>();
let black = starknet::contract_address_const::<0x02>();
We should list both Components and Systems in arrays, with each having CLASS_HASH as elements.
// components
let mut components = array::ArrayTrait::new();
components.append(game::TEST_CLASS_HASH);
components.append(game_turn::TEST_CLASS_HASH);
components.append(square::TEST_CLASS_HASH);
//systems
let mut systems = array::ArrayTrait::new();
systems.append(initiate_system::TEST_CLASS_HASH);
systems.append(move_system::TEST_CLASS_HASH);
Next, we'll create our game world.
let world = spawn_test_world(components, systems);
We use initiate_system to put our Square pieces on the board. Each Square holds a piece. The system's execute function needs some input, which we give it as calldata.
// initiate
let mut calldata = array::ArrayTrait::<core::felt252>::new();
calldata.append(white.into());
calldata.append(black.into());
world.execute('initiate_system'.into(), calldata);
Let's check if a White pawn is at (0,1). Remember, to get a piece that exists on the square, you need to use the keys of the Square component, which are game_id, x, and y. Do the same check for the Black Pawn.
//White pawn is now in (0,1)
let a2 = get!(world, (game_id, 0, 1), (Square));
match a2.piece {
Option::Some(piece) => {
assert(piece == PieceType::WhitePawn, "should be White Pawn in (0,1)");
},
Option::None(_) => assert(false, 'should have piece in (0,1)),
};
After setting up the board, use move_system to make moves. Provide the current position, the next position, the player's address, and the game id.
//Move White Pawn to (0,3)
let mut move_calldata = array::ArrayTrait::<core::felt252>::new();
move_calldata.append(0);
move_calldata.append(1);
move_calldata.append(0);
move_calldata.append(3);
move_calldata.append(white.into());
move_calldata.append(game_id);
world.execute('move_system'.into(), move_calldata);
Keep moving pieces and checking if they're in the right places.
Congratulations!
You've made the basic contracts for a chess game using the Dojo engine! This tutorial was just the beginning. There are many ways to make the game better, like optimizing parts, adding checks, or considering special cases. If you want to do more with this chess game, try these challenges:
- Make an
initiate_systemthat uses lazy init. If you're unsure about lazy init, read up on it. This can help make your game actions more efficient. - Add a checkmate feature. Our game doesn't end now, so decide when it should!
- Include special moves like castling, En Passant Capture, or Pawn Promotion.
- Make your own chess rules! You could even create your own version of the immortal game
Lastly, share your project with others in the Dojo community!