First commit tic-tac-toe

This commit is contained in:
Ivan Folgueira Bande 2024-11-05 12:44:44 +01:00
parent dfe068222d
commit 2ed472ef8d
No known key found for this signature in database
GPG Key ID: 3C117481F89E24A7
8 changed files with 277 additions and 8 deletions

View File

@ -2,4 +2,4 @@
members = [
"basic"
]
, "tic-tac-toe"]

View File

@ -0,0 +1,11 @@
[package]
name = "tic-tac-toe"
version = "0.1.0"
edition = "2021"
[dependencies]
termcolor = "0.3"
waku = { path = "../../waku-bindings", package = "waku-bindings" }
serde_json = "1.0"
ark-std = "0.4"
ctrlc = "3.2.4"

View File

@ -0,0 +1,224 @@
extern crate termcolor;
use std::io::Write;
use std::str::from_utf8;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use waku::{
waku_new, Event, WakuNodeConfig,
LibwakuResponse, Multiaddr, Running, WakuNodeHandle,
};
fn greeting() {
println!(
"\nRust TicTacToe\n\
--------------\n\
A simple game written in the rust programming language.\n\
Credits to: https://github.com/flofriday/tictactoe"
)
}
fn print_player(player: &char) {
let mut stdout = StandardStream::stdout(ColorChoice::Always);
if player == &'X' {
stdout
.set_color(ColorSpec::new().set_fg(Some(Color::Blue)))
.unwrap();
} else if player == &'O' {
stdout
.set_color(ColorSpec::new().set_fg(Some(Color::Green)))
.unwrap();
}
write!(&mut stdout, "{}", player).unwrap();
stdout.reset().unwrap();
}
fn draw(board: &[char]) {
println!("\n");
for i in (0..3).rev() {
let offset = i * 3;
print!("-------------\n| ");
print_player(&board[offset]);
print!(" | ");
print_player(&board[offset + 1]);
print!(" | ");
print_player(&board[offset + 2]);
println!(" |");
}
println!("-------------");
}
fn ask_user(board: &mut [char], player: char) {
loop {
print!("Player '");
print_player(&player);
println!("', enter a number: ");
let mut input = String::new();
if std::io::stdin().read_line(&mut input).is_err() {
println!("Couldn't read line! Try again.");
continue;
}
if let Ok(number) = input.trim().parse::<usize>() {
if number < 1 || number > 9 {
println!("The field number must be between 1 and 9.");
continue;
}
let number = number - 1;
if board[number] == 'X' || board[number] == 'O' {
print!("This field is already taken by '");
print_player(&board[number]);
println!("'.");
continue;
}
board[number] = player;
break;
} else {
println!("Only numbers are allowed.");
continue;
}
}
}
fn has_won(board: &[char]) -> bool {
for tmp in 0..3 {
if board[tmp] == board[tmp + 3] && board[tmp] == board[tmp + 6] {
return true;
}
let tmp = tmp * 3;
if board[tmp] == board[tmp + 1] && board[tmp] == board[tmp + 2] {
return true;
}
}
if (board[0] == board[4] && board[0] == board[8])
|| (board[2] == board[4] && board[2] == board[6])
{
return true;
}
false
}
#[inline(always)]
fn is_over(board: &[char]) -> bool {
board.iter().all(|&v| v == 'X' || v == 'O')
}
// Return true if the game should end.
// false otherwise
fn game_logic(player: &mut char, board: &mut [char],
topic: &str, waku: &WakuNodeHandle<Running>) -> bool {
// Ask for user input
ask_user(board, *player);
let board_string: String = board.iter().collect();
let board_str_slice: &str = &board_string;
let _ = waku.relay_publish_txt(topic, &board_str_slice, "tic-tac-toe-example", None);
// Check if a player won
if has_won(&board) {
draw(&board);
print!("Player '");
print_player(&player);
println!("' won! \\(^.^)/");
return true;
}
// Check if all fields are used
if is_over(&board) {
draw(&board);
println!("All fields are used. No one won. (._.)");
return true;
}
// Switch player
*player = if *player == 'X' { 'O' } else { 'X' };
return false;
}
fn main() {
let mut board = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
let mut player = 'X';
let topic = "/waku/2/rs/16/64";
// Create a Waku instance
let waku = waku_new(Some(WakuNodeConfig {
port: Some(60010),
cluster_id: Some(16),
..Default::default()
}))
.expect("should instantiate");
// ctrlc::set_handler(move ||{
// println!("Ctrl+C detected. Exiting gracefully...");
// // waku.stop();
// }).expect("Error setting Ctrl+C handler");
let waku = waku.start().expect("waku should start");
// Establish a closure that handles the incoming messages
waku.set_event_callback(&|response| {
if let LibwakuResponse::Success(v) = response {
let event: Event =
serde_json::from_str(v.unwrap().as_str()).expect("Parsing event to succeed");
match event {
Event::WakuMessage(evt) => {
println!("WakuMessage event received: {:?}", evt.waku_message);
let message = evt.waku_message;
let payload = message.payload.to_vec();
match from_utf8(&payload) {
Ok(msg) => {
println!("::::::::::::::::::::::::::::::::::::::::::::::::::::");
println!("Message Received: {}", msg);
println!("::::::::::::::::::::::::::::::::::::::::::::::::::::");
// game_logic(&mut player, &mut board, topic, &waku);
}
Err(e) => {
eprintln!("Failed to decode payload as UTF-8: {}", e);
// Handle the error as needed, or just log and skip
}
}
}
Event::Unrecognized(err) => panic!("Unrecognized waku event: {:?}", err),
_ => panic!("event case not expected"),
};
}
});
waku.relay_subscribe(&topic).expect("waku should subscribe");
let target_node_multi_addr =
"/dns4/store-01.do-ams3.status.staging.status.im/tcp/30303/p2p/16Uiu2HAm3xVDaz6SRJ6kErwC21zBJEZjavVXg7VSkoWzaV1aMA3F"
.parse::<Multiaddr>().expect("parse multiaddress");
waku.connect(&target_node_multi_addr, None)
.expect("waku should connect to other node");
// Welcome the player
greeting();
loop {
// Draw the field
draw(&board);
if game_logic(&mut player, &mut board, topic, &waku) {
break;
}
}
}

View File

@ -0,0 +1,5 @@
use waku::{
waku_destroy, waku_new, Encoding, Event, WakuContentTopic, WakuMessage, WakuNodeConfig, LibwakuResponse,
};
pub mut waku: WakuNodeHandle<State: WakuNodeState>;

View File

@ -2,7 +2,7 @@
//!
//! Implementation on top of [`waku-bindings`](https://rfc.vac.dev/spec/36/)
mod general;
mod node;
pub mod node;
pub mod utils;
// Re-export the LibwakuResponse type to make it accessible outside this module

View File

@ -23,6 +23,10 @@ pub use config::WakuNodeConfig;
pub use events::{Event, WakuMessageEvent};
pub use relay::waku_create_content_topic;
use crate::WakuContentTopic;
use crate::Encoding;
use std::time::SystemTime;
/// Marker trait to disallow undesired waku node states in the handle
pub trait WakuNodeState {}
@ -65,7 +69,7 @@ impl WakuNodeHandle<Initialized> {
}
}
impl WakuNodeHandle<Running> {
impl<Running: WakuNodeState> WakuNodeHandle<Running> {
/// Stops a Waku node
/// as per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_stop)
pub fn stop(self) -> Result<WakuNodeHandle<Initialized>> {
@ -95,20 +99,45 @@ impl WakuNodeHandle<Running> {
peers::waku_connect(&self.ctx, address, timeout)
}
pub fn relay_publish_txt(
&self,
pubsub_topic: &str,
msg_txt: &str,
content_topic_name: &'static str,
timeout: Option<Duration>,
) -> Result<MessageHash> {
let content_topic = WakuContentTopic::new("waku", "2", content_topic_name, Encoding::Proto);
let message = WakuMessage::new(
msg_txt,
content_topic,
0,
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis()
.try_into()
.unwrap(),
Vec::new(),
false,
);
relay::waku_relay_publish_message(&self.ctx, &message, pubsub_topic, timeout)
}
/// Publish a message using Waku Relay.
/// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_relay_publishchar-messagejson-char-pubsubtopic-int-timeoutms)
/// The pubsub_topic parameter is optional and if not specified it will be derived from the contentTopic.
pub fn relay_publish_message(
&self,
message: &WakuMessage,
pubsub_topic: &String,
pubsub_topic: &str,
timeout: Option<Duration>,
) -> Result<MessageHash> {
relay::waku_relay_publish_message(&self.ctx, message, pubsub_topic, timeout)
}
/// Subscribe to WakuRelay to receive messages matching a content filter.
pub fn relay_subscribe(&self, pubsub_topic: &String) -> Result<()> {
pub fn relay_subscribe(&self, pubsub_topic: &str) -> Result<()> {
relay::waku_relay_subscribe(&self.ctx, pubsub_topic)
}

View File

@ -60,7 +60,7 @@ pub fn waku_create_content_topic(
pub fn waku_relay_publish_message(
ctx: &WakuNodeContext,
message: &WakuMessage,
pubsub_topic: &String,
pubsub_topic: &str,
timeout: Option<Duration>,
) -> Result<MessageHash> {
let pubsub_topic = pubsub_topic.to_string();
@ -105,7 +105,7 @@ pub fn waku_relay_publish_message(
handle_response(code, result)
}
pub fn waku_relay_subscribe(ctx: &WakuNodeContext, pubsub_topic: &String) -> Result<()> {
pub fn waku_relay_subscribe(ctx: &WakuNodeContext, pubsub_topic: &str) -> Result<()> {
let pubsub_topic = pubsub_topic.to_string();
let pubsub_topic_ptr = CString::new(pubsub_topic)
.expect("CString should build properly from pubsub topic")

View File

@ -57,7 +57,7 @@ async fn test_echo_messages(
node2.set_event_callback(&closure); // Set the event callback with the closure
let topic = TEST_PUBSUBTOPIC.to_string();
let topic = TEST_PUBSUBTOPIC;
node1.relay_subscribe(&topic).unwrap();
node2.relay_subscribe(&topic).unwrap();