Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: submit_answer #266

Merged
merged 3 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions onchain/src/contracts/lyricsflip.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -402,13 +402,38 @@ pub mod LyricsFlip {
cards.span()
}

fn get_player_stat(self: @ContractState, player: ContractAddress) -> PlayerStats {
self.player_stats.entry(player).read()
fn submit_answer(ref self: ContractState, round_id: u64, answer: Answer) -> bool {
// Verify round exists
assert(self.rounds.entry(round_id).round_id.read() != 0, Errors::NON_EXISTING_ROUND);

let caller_address = get_caller_address();

// Verify caller is a participant
assert(self._is_round_player(round_id, caller_address), Errors::NOT_A_PARTICIPANT);

let round = self.rounds.entry(round_id).read();

// Verify round has started and not completed
assert(round.is_started, Errors::ROUND_NOT_STARTED);
assert(!round.is_completed, Errors::ROUND_COMPLETED);

// Get current card index and card
let current_index = round.next_card_index - 1;

let round_cards = self.round_cards.entry(round_id);
let current_card_id = round_cards.at((current_index).into()).read();
let current_card = self.cards.entry(current_card_id).read();

// Compare answer with card data
match answer {
Answer::Artist(value) => { value == current_card.artist },
Answer::Year(value) => { value == current_card.year },
Answer::Title(value) => { value == current_card.title }
}
}

// TODO
fn submit_answer(self: @ContractState, answer: Answer) -> bool {
false
fn get_player_stat(self: @ContractState, player: ContractAddress) -> PlayerStats {
self.player_stats.entry(player).read()
}
}

Expand Down
5 changes: 1 addition & 4 deletions onchain/src/interfaces/lyricsflip.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ pub trait ILyricsFlip<TContractState> {
fn set_role(
ref self: TContractState, recipient: ContractAddress, role: felt252, is_enable: bool,
);

fn get_player_stat(self: @TContractState, player: ContractAddress) -> PlayerStats;

//TODO
fn submit_answer(self: @TContractState, answer: Answer) -> bool;
fn submit_answer(ref self: TContractState, round_id: u64, answer: Answer) -> bool;
}
276 changes: 275 additions & 1 deletion onchain/src/tests/test_lyricsflip.cairo
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use LyricsFlip::{InternalFunctions, InternalFunctionsTrait};
use lyricsflip::contracts::lyricsflip::LyricsFlip;
use lyricsflip::interfaces::lyricsflip::{ILyricsFlipDispatcher, ILyricsFlipDispatcherTrait};
use lyricsflip::utils::types::{Card, Genre};
use lyricsflip::utils::types::{Card, Genre, Answer};
use snforge_std::{
ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, declare, spy_events,
start_cheat_block_timestamp_global, start_cheat_caller_address,
Expand Down Expand Up @@ -1084,3 +1084,277 @@ fn test_start_round_updates_player_stats() {

assert(new_player_two_stats.total_rounds == 1, 'Player two stats not updated');
}
#[test]
fn test_submit_correct_artist_answer() {
let lyricsflip = deploy();

// Setup admin and initial card
start_cheat_caller_address(lyricsflip.contract_address, OWNER());
lyricsflip.set_role(ADMIN_ADDRESS(), ADMIN_ROLE, true);
stop_cheat_caller_address(lyricsflip.contract_address);

start_cheat_caller_address(lyricsflip.contract_address, ADMIN_ADDRESS());
for i in 0
..5_u64 {
let card = Card {
card_id: i.into(),
genre: Genre::HipHop,
artist: 'Test Artist',
title: "Test Title",
year: 2000,
lyrics: "Test Lyrics",
};
lyricsflip.add_card(card);
};
lyricsflip.set_cards_per_round(5);
stop_cheat_caller_address(lyricsflip.contract_address);

// Create and start round
start_cheat_caller_address(lyricsflip.contract_address, PLAYER_1());
let round_id = lyricsflip.create_round(Option::Some(Genre::HipHop), 1);
lyricsflip.start_round(round_id);
lyricsflip.next_card(round_id);

let answer = Answer::Artist('Test Artist');
let result = lyricsflip.submit_answer(round_id, answer);
assert(result == true, 'Should accept correct artist');

stop_cheat_caller_address(lyricsflip.contract_address);
}

#[test]
fn test_submit_correct_year_answer() {
let lyricsflip = deploy();

// Setup admin and initial card
start_cheat_caller_address(lyricsflip.contract_address, OWNER());
lyricsflip.set_role(ADMIN_ADDRESS(), ADMIN_ROLE, true);
stop_cheat_caller_address(lyricsflip.contract_address);

start_cheat_caller_address(lyricsflip.contract_address, ADMIN_ADDRESS());
for i in 0
..5_u64 {
let card = Card {
card_id: i.into(),
genre: Genre::HipHop,
artist: 'Test Artist',
title: "Test Title",
year: 2000,
lyrics: "Test Lyrics",
};
lyricsflip.add_card(card);
};
lyricsflip.set_cards_per_round(5);
stop_cheat_caller_address(lyricsflip.contract_address);

// Create and start round
start_cheat_caller_address(lyricsflip.contract_address, PLAYER_1());
let round_id = lyricsflip.create_round(Option::Some(Genre::HipHop), 1);
lyricsflip.start_round(round_id);
lyricsflip.next_card(round_id);

let answer = Answer::Year(2000);
let result = lyricsflip.submit_answer(round_id, answer);
assert(result == true, 'Should accept correct year');

stop_cheat_caller_address(lyricsflip.contract_address);
}

#[test]
fn test_submit_correct_title_answer() {
let lyricsflip = deploy();

// Setup admin and initial card
start_cheat_caller_address(lyricsflip.contract_address, OWNER());
lyricsflip.set_role(ADMIN_ADDRESS(), ADMIN_ROLE, true);
stop_cheat_caller_address(lyricsflip.contract_address);

start_cheat_caller_address(lyricsflip.contract_address, ADMIN_ADDRESS());
for i in 0
..5_u64 {
let card = Card {
card_id: i.into(),
genre: Genre::HipHop,
artist: 'Test Artist',
title: "Test Title",
year: 2000,
lyrics: "Test Lyrics",
};
lyricsflip.add_card(card);
};
lyricsflip.set_cards_per_round(5);
stop_cheat_caller_address(lyricsflip.contract_address);

// Create and start round
start_cheat_caller_address(lyricsflip.contract_address, PLAYER_1());
let round_id = lyricsflip.create_round(Option::Some(Genre::HipHop), 1);
lyricsflip.start_round(round_id);
lyricsflip.next_card(round_id);

let answer = Answer::Title("Test Title");
let result = lyricsflip.submit_answer(round_id, answer);
assert(result == true, 'Should accept correct title');

stop_cheat_caller_address(lyricsflip.contract_address);
}

#[test]
fn test_submit_incorrect_answer() {
let lyricsflip = deploy();

// Setup admin and initial card
start_cheat_caller_address(lyricsflip.contract_address, OWNER());
lyricsflip.set_role(ADMIN_ADDRESS(), ADMIN_ROLE, true);
stop_cheat_caller_address(lyricsflip.contract_address);

start_cheat_caller_address(lyricsflip.contract_address, ADMIN_ADDRESS());
for i in 0
..5_u64 {
let card = Card {
card_id: i.into(),
genre: Genre::HipHop,
artist: 'Test Artist',
title: "Test Title",
year: 2000,
lyrics: "Test Lyrics",
};
lyricsflip.add_card(card);
};
lyricsflip.set_cards_per_round(5);
stop_cheat_caller_address(lyricsflip.contract_address);

// Create and start round
start_cheat_caller_address(lyricsflip.contract_address, PLAYER_1());
let round_id = lyricsflip.create_round(Option::Some(Genre::HipHop), 1);
lyricsflip.start_round(round_id);
lyricsflip.next_card(round_id);

// Test incorrect answers for each type
let wrong_artist = Answer::Artist('WrongArtist');
let result = lyricsflip.submit_answer(round_id, wrong_artist);
assert(result == false, 'Should reject wrong artist');

stop_cheat_caller_address(lyricsflip.contract_address);
}

#[test]
#[should_panic(expected: ('Round does not exists',))]
fn test_submit_answer_should_panic_with_non_existing_round() {
let lyricsflip = deploy();

start_cheat_caller_address(lyricsflip.contract_address, PLAYER_1());
let answer = Answer::Year(2000);
lyricsflip.submit_answer(999, answer);
stop_cheat_caller_address(lyricsflip.contract_address);
}

#[test]
#[should_panic(expected: ('Not a participant',))]
fn test_submit_answer_should_panic_with_non_participant() {
let lyricsflip = deploy();

// Setup admin and initial card
start_cheat_caller_address(lyricsflip.contract_address, OWNER());
lyricsflip.set_role(ADMIN_ADDRESS(), ADMIN_ROLE, true);
stop_cheat_caller_address(lyricsflip.contract_address);

start_cheat_caller_address(lyricsflip.contract_address, ADMIN_ADDRESS());
let test_card = Card {
card_id: 1,
genre: Genre::HipHop,
artist: 'Test Artist',
title: "Test Title",
year: 2000,
lyrics: "Test Lyrics",
};
lyricsflip.add_card(test_card);
lyricsflip.set_cards_per_round(1);
stop_cheat_caller_address(lyricsflip.contract_address);

// PLAYER_1 creates round
start_cheat_caller_address(lyricsflip.contract_address, PLAYER_1());
let round_id = lyricsflip.create_round(Option::Some(Genre::HipHop), 1);
lyricsflip.start_round(round_id);
stop_cheat_caller_address(lyricsflip.contract_address);

// PLAYER_2 tries to submit answer without joining
start_cheat_caller_address(lyricsflip.contract_address, PLAYER_2());
let answer = Answer::Year(2000);
lyricsflip.submit_answer(round_id, answer);
stop_cheat_caller_address(lyricsflip.contract_address);
}

#[test]
#[should_panic(expected: ('Round not started',))]
fn test_submit_answer_should_panic_with_non_started_round() {
let lyricsflip = deploy();

// Setup admin and initial card
start_cheat_caller_address(lyricsflip.contract_address, OWNER());
lyricsflip.set_role(ADMIN_ADDRESS(), ADMIN_ROLE, true);
stop_cheat_caller_address(lyricsflip.contract_address);

start_cheat_caller_address(lyricsflip.contract_address, ADMIN_ADDRESS());
let test_card = Card {
card_id: 1,
genre: Genre::HipHop,
artist: 'Test Artist',
title: "Test Title",
year: 2000,
lyrics: "Test Lyrics",
};
lyricsflip.add_card(test_card);
lyricsflip.set_cards_per_round(1);
stop_cheat_caller_address(lyricsflip.contract_address);

// Create round but don't start it
start_cheat_caller_address(lyricsflip.contract_address, PLAYER_1());
let round_id = lyricsflip.create_round(Option::Some(Genre::HipHop), 1);

// Try to submit answer
let answer = Answer::Year(2000);
lyricsflip.submit_answer(round_id, answer);
stop_cheat_caller_address(lyricsflip.contract_address);
}

#[test]
#[should_panic(expected: ('Round already completed',))]
fn test_submit_answer_should_panic_with_completed_round() {
let lyricsflip = deploy();

// Setup admin and initial card
start_cheat_caller_address(lyricsflip.contract_address, OWNER());
lyricsflip.set_role(ADMIN_ADDRESS(), ADMIN_ROLE, true);
stop_cheat_caller_address(lyricsflip.contract_address);

start_cheat_caller_address(lyricsflip.contract_address, ADMIN_ADDRESS());
for i in 0
..5_u64 {
let card = Card {
card_id: i.into(),
genre: Genre::HipHop,
artist: 'Test Artist',
title: "Test Title",
year: 2000,
lyrics: "Test Lyrics",
};
lyricsflip.add_card(card);
};
lyricsflip.set_cards_per_round(5);
stop_cheat_caller_address(lyricsflip.contract_address);

// Create and start round
start_cheat_caller_address(lyricsflip.contract_address, PLAYER_1());
let round_id = lyricsflip.create_round(Option::Some(Genre::HipHop), 1);
lyricsflip.start_round(round_id);

// Complete round
for _i in 0..5_u64 {
lyricsflip.next_card(round_id);
};

// Try to submit another answer after round completion
let another_answer = Answer::Title("Test Title");
lyricsflip.submit_answer(round_id, another_answer);
stop_cheat_caller_address(lyricsflip.contract_address);
}
6 changes: 3 additions & 3 deletions onchain/src/utils/types.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ pub struct QuestionCard<T> {

#[derive(Drop, Serde, starknet::Store)]
pub enum Answer {
Felt252: felt252,
U64: u64,
Bytes: ByteArray,
Artist: felt252,
Year: u64,
Title: ByteArray,
}