From 6fe5a268da3111a408db2ad55946c449e303d23b Mon Sep 17 00:00:00 2001 From: Henri Bourcereau Date: Sun, 14 Jan 2024 12:24:57 +0100 Subject: [PATCH] doc --- doc/blog/pourquoi-le-trictrac.md | 9 ++ doc/refs/renet_echo.rs | 182 +++++++++++++++++++++++++++++++ doc/traité.md | 29 +++++ store/src/dice.rs | 40 +------ store/src/game.rs | 6 + 5 files changed, 229 insertions(+), 37 deletions(-) create mode 100644 doc/blog/pourquoi-le-trictrac.md create mode 100644 doc/refs/renet_echo.rs create mode 100644 doc/traité.md diff --git a/doc/blog/pourquoi-le-trictrac.md b/doc/blog/pourquoi-le-trictrac.md new file mode 100644 index 0000000..9d653b7 --- /dev/null +++ b/doc/blog/pourquoi-le-trictrac.md @@ -0,0 +1,9 @@ +# Pourquoi le trictrac + +Je vais montrer pourquoi il faut redécouvrir le trictrac, pourquoi il pourrait être votre nouveau jeu favori. +Je vais montrer pourquoi il est intéressant, complexe pour les humains comme les machines. +En quoi il diffère du backgammon de manière avantageuse. Pourquoi il a quasiment disparu. + +Le backgammon était connu à la grande époque du trictrac. Il ne lui manquait que le dé doubleur, apparu au début du XXe siècle, qui a ajouté les prises de décision méta, que le trictrac possèdait déjà sur plusieurs niveaux (via la gestion des bredouilles en fin de partie par exemple). + +Le hasard des dés permet au joueur faible d'espérer la victoire même en fin de partie (cf. Le tour du monde en 80 jeux). diff --git a/doc/refs/renet_echo.rs b/doc/refs/renet_echo.rs new file mode 100644 index 0000000..366a391 --- /dev/null +++ b/doc/refs/renet_echo.rs @@ -0,0 +1,182 @@ +use std::{ + collections::HashMap, + net::{SocketAddr, UdpSocket}, + sync::mpsc::{self, Receiver, TryRecvError}, + thread, + time::{Duration, Instant, SystemTime}, +}; + +use renet::{ + transport::{ + ClientAuthentication, NetcodeClientTransport, NetcodeServerTransport, ServerAuthentication, ServerConfig, NETCODE_USER_DATA_BYTES, + }, + ClientId, ConnectionConfig, DefaultChannel, RenetClient, RenetServer, ServerEvent, +}; + +// Helper struct to pass an username in the user data +struct Username(String); + +impl Username { + fn to_netcode_user_data(&self) -> [u8; NETCODE_USER_DATA_BYTES] { + let mut user_data = [0u8; NETCODE_USER_DATA_BYTES]; + if self.0.len() > NETCODE_USER_DATA_BYTES - 8 { + panic!("Username is too big"); + } + user_data[0..8].copy_from_slice(&(self.0.len() as u64).to_le_bytes()); + user_data[8..self.0.len() + 8].copy_from_slice(self.0.as_bytes()); + + user_data + } + + fn from_user_data(user_data: &[u8; NETCODE_USER_DATA_BYTES]) -> Self { + let mut buffer = [0u8; 8]; + buffer.copy_from_slice(&user_data[0..8]); + let mut len = u64::from_le_bytes(buffer) as usize; + len = len.min(NETCODE_USER_DATA_BYTES - 8); + let data = user_data[8..len + 8].to_vec(); + let username = String::from_utf8(data).unwrap(); + Self(username) + } +} + +fn main() { + env_logger::init(); + println!("Usage: server [SERVER_PORT] or client [SERVER_ADDR] [USER_NAME]"); + let args: Vec = std::env::args().collect(); + + let exec_type = &args[1]; + match exec_type.as_str() { + "client" => { + let server_addr: SocketAddr = args[2].parse().unwrap(); + let username = Username(args[3].clone()); + client(server_addr, username); + } + "server" => { + let server_addr: SocketAddr = format!("0.0.0.0:{}", args[2]).parse().unwrap(); + server(server_addr); + } + _ => { + println!("Invalid argument, first one must be \"client\" or \"server\"."); + } + } +} + +const PROTOCOL_ID: u64 = 7; + +fn server(public_addr: SocketAddr) { + let connection_config = ConnectionConfig::default(); + let mut server: RenetServer = RenetServer::new(connection_config); + + let current_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let server_config = ServerConfig { + current_time, + max_clients: 64, + protocol_id: PROTOCOL_ID, + public_addresses: vec![public_addr], + authentication: ServerAuthentication::Unsecure, + }; + let socket: UdpSocket = UdpSocket::bind(public_addr).unwrap(); + + let mut transport = NetcodeServerTransport::new(server_config, socket).unwrap(); + + let mut usernames: HashMap = HashMap::new(); + let mut received_messages = vec![]; + let mut last_updated = Instant::now(); + + loop { + let now = Instant::now(); + let duration = now - last_updated; + last_updated = now; + + server.update(duration); + transport.update(duration, &mut server).unwrap(); + + received_messages.clear(); + + while let Some(event) = server.get_event() { + match event { + ServerEvent::ClientConnected { client_id } => { + let user_data = transport.user_data(client_id).unwrap(); + let username = Username::from_user_data(&user_data); + usernames.insert(client_id, username.0); + println!("Client {} connected.", client_id) + } + ServerEvent::ClientDisconnected { client_id, reason } => { + println!("Client {} disconnected: {}", client_id, reason); + usernames.remove_entry(&client_id); + } + } + } + + for client_id in server.clients_id() { + while let Some(message) = server.receive_message(client_id, DefaultChannel::ReliableOrdered) { + let text = String::from_utf8(message.into()).unwrap(); + let username = usernames.get(&client_id).unwrap(); + println!("Client {} ({}) sent text: {}", username, client_id, text); + let text = format!("{}: {}", username, text); + received_messages.push(text); + } + } + + for text in received_messages.iter() { + server.broadcast_message(DefaultChannel::ReliableOrdered, text.as_bytes().to_vec()); + } + + transport.send_packets(&mut server); + thread::sleep(Duration::from_millis(50)); + } +} + +fn client(server_addr: SocketAddr, username: Username) { + let connection_config = ConnectionConfig::default(); + let mut client = RenetClient::new(connection_config); + + let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); + let current_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let client_id = current_time.as_millis() as u64; + let authentication = ClientAuthentication::Unsecure { + server_addr, + client_id, + user_data: Some(username.to_netcode_user_data()), + protocol_id: PROTOCOL_ID, + }; + + let mut transport = NetcodeClientTransport::new(current_time, authentication, socket).unwrap(); + let stdin_channel: Receiver = spawn_stdin_channel(); + + let mut last_updated = Instant::now(); + loop { + let now = Instant::now(); + let duration = now - last_updated; + last_updated = now; + + client.update(duration); + transport.update(duration, &mut client).unwrap(); + + if transport.is_connected() { + match stdin_channel.try_recv() { + Ok(text) => client.send_message(DefaultChannel::ReliableOrdered, text.as_bytes().to_vec()), + Err(TryRecvError::Empty) => {} + Err(TryRecvError::Disconnected) => panic!("Channel disconnected"), + } + + while let Some(text) = client.receive_message(DefaultChannel::ReliableOrdered) { + let text = String::from_utf8(text.into()).unwrap(); + println!("{}", text); + } + } + + transport.send_packets(&mut client).unwrap(); + thread::sleep(Duration::from_millis(50)); + } +} + +fn spawn_stdin_channel() -> Receiver { + let (tx, rx) = mpsc::channel::(); + thread::spawn(move || loop { + let mut buffer = String::new(); + std::io::stdin().read_line(&mut buffer).unwrap(); + tx.send(buffer.trim_end().to_string()).unwrap(); + }); + rx +} diff --git a/doc/traité.md b/doc/traité.md new file mode 100644 index 0000000..fb8dd47 --- /dev/null +++ b/doc/traité.md @@ -0,0 +1,29 @@ +En 12 chapitres (trous) de 12 sous-chapitres (points / niveaux de compréhension) ? + +Les règles +- le matériel +- le mouvement +- les points +- les écoles +- les combinaisons + + +La stratégie +- probabilités +- arbres de décision +- l'entraînement + +L'encyclopédie +- comparaison avec d'autres jeux + - échecs/go +- histoire + - traités +- vocabulaire +- l'esthétique +- l'étiquette +- ressources web + - wikipedia + - l'encyclopédie des jeux + videos youtube + - le dictionnaire du trictrac +- fabriquer un boîtier/plateau de jeu +- jouer en ligne diff --git a/store/src/dice.rs b/store/src/dice.rs index 3c3a006..5266e32 100644 --- a/store/src/dice.rs +++ b/store/src/dice.rs @@ -4,15 +4,11 @@ use serde::{Deserialize, Serialize}; /// Represents the two dices /// -/// Backgammon is always played with two dices. +/// Trictrac is always played with two dices. #[derive(Debug, Clone, Copy, Serialize, PartialEq, Deserialize, Default)] pub struct Dices { /// The two dice values pub values: (u8, u8), - /// Boolean indicating whether the dices have been consumed already. We use a tuple - /// of four booleans in case the dices are equal, in which case we have four dices - /// to play. - pub consumed: (bool, bool, bool, bool), } impl Dices { /// Roll the dices which generates two random numbers between 1 and 6, replicating a perfect @@ -23,17 +19,8 @@ impl Dices { let v = (between.sample(&mut rng), between.sample(&mut rng)); - // if both dices are equal, we have four dices to play - if v.0 == v.1 { - Dices { - values: (v.0, v.1), - consumed: (false, false, false, false), - } - } else { - Dices { - values: (v.0, v.1), - consumed: (false, false, true, true), - } + Dices { + values: (v.0, v.1), } } } @@ -55,25 +42,4 @@ mod tests { assert!(dices.values.1 >= 1 && dices.values.1 <= 6); } - #[test] - fn test_roll_consumed() { - let dices = Dices::default().roll(); - if dices.values.0 == dices.values.1 { - assert_eq!(dices.consumed, (false, false, false, false)); - } else { - assert_eq!(dices.consumed, (false, false, true, true)); - } - } - - #[test] - fn test_roll_consumed1() { - for _i in 0..100 { - let dices = Dices::default().roll(); - if dices.values.0 == dices.values.1 { - assert_eq!(dices.consumed, (false, false, false, false)); - } else { - assert_eq!(dices.consumed, (false, false, true, true)); - } - } - } } diff --git a/store/src/game.rs b/store/src/game.rs index fb53382..6791af1 100644 --- a/store/src/game.rs +++ b/store/src/game.rs @@ -108,6 +108,12 @@ impl GameState { pos_bits.append(&mut step_bits.into()); // dice roll -> 4 bits + let mut dice_bits = match self.dices { + TurnStage::RollDice => [false, false], + TurnStage::MarkPoints => [false, true], + TurnStage::Move => [true, false], + }; + pos_bits.append(&mut step_bits.into()); // points 10bits x2 joueurs = 20bits // * points -> 4bits // * trous -> 4bits