doc
This commit is contained in:
parent
8dbaf597c9
commit
6fe5a268da
9
doc/blog/pourquoi-le-trictrac.md
Normal file
9
doc/blog/pourquoi-le-trictrac.md
Normal 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
182
doc/refs/renet_echo.rs
Normal 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
29
doc/traité.md
Normal 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
|
||||||
|
|
@ -4,15 +4,11 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Represents the two dices
|
/// 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)]
|
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Deserialize, Default)]
|
||||||
pub struct Dices {
|
pub struct Dices {
|
||||||
/// The two dice values
|
/// The two dice values
|
||||||
pub values: (u8, u8),
|
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 {
|
impl Dices {
|
||||||
/// Roll the dices which generates two random numbers between 1 and 6, replicating a perfect
|
/// 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));
|
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 {
|
Dices {
|
||||||
values: (v.0, v.1),
|
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);
|
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,12 @@ impl GameState {
|
||||||
pos_bits.append(&mut step_bits.into());
|
pos_bits.append(&mut step_bits.into());
|
||||||
|
|
||||||
// dice roll -> 4 bits
|
// 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 10bits x2 joueurs = 20bits
|
||||||
// * points -> 4bits
|
// * points -> 4bits
|
||||||
// * trous -> 4bits
|
// * trous -> 4bits
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue