This commit is contained in:
Henri Bourcereau 2024-01-14 12:24:57 +01:00
parent 8dbaf597c9
commit 6fe5a268da
5 changed files with 229 additions and 37 deletions

View file

@ -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).

182
doc/refs/renet_echo.rs Normal file
View file

@ -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<String> = 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<ClientId, String> = 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<String> = 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<String> {
let (tx, rx) = mpsc::channel::<String>();
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
}

29
doc/traité.md Normal file
View file

@ -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

View file

@ -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),
}
}
}
}
@ -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));
}
}
}
}

View file

@ -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