Skip to content

Instantly share code, notes, and snippets.

@jonathanpallant
Created May 26, 2026 10:14
Show Gist options
  • Select an option

  • Save jonathanpallant/1b7ef20d44f43f49e0a7d50bfe24f6fd to your computer and use it in GitHub Desktop.

Select an option

Save jonathanpallant/1b7ef20d44f43f49e0a7d50bfe24f6fd to your computer and use it in GitHub Desktop.
[package]
name = "network-green-yellow"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1.0.102"
rand = "0.10.1"
//! A library version of the Green and Yellow Game
use rand::prelude::*;
#[derive(Debug, Clone)]
pub struct Game {
secret: [u8; 4],
}
impl Game {
/// Create a new game with a random number inside
pub fn new() -> Game {
let mut rng = rand::rng();
let possible_digits: Vec<u8> = (0..=9).collect();
let mut secret = [0u8; 4];
for digit in secret.iter_mut() {
*digit = *possible_digits.choose(&mut rng).unwrap();
}
Game { secret }
}
/// Make a guess and see how good it was
pub fn calc_green_and_yellow(&self, guess: &[u8; 4]) -> String {
let mut result = ["⬜"; 4];
let mut secret_handled = [false; 4];
for i in 0..guess.len() {
if guess[i] == self.secret[i] {
// that's a match
result[i] = "🟩";
// don't match this secret digit again
secret_handled[i] = true;
}
}
'guess: for g_idx in 0..guess.len() {
// only process guess digits we haven't already dealt with
if result[g_idx] == "🟩" {
continue;
}
for s_idx in 0..self.secret.len() {
// only process secret digits we haven't already dealt with
if secret_handled[s_idx] {
continue;
}
if guess[g_idx] == self.secret[s_idx] {
// put a yellow block in for this guess
result[g_idx] = "🟨";
// never match this secret digit again
secret_handled[s_idx] = true;
// stop comparing this guessed digit to any other secret digits
continue 'guess;
}
}
}
result.join("")
}
}
//! Start a network server to play Green and Yellow
use std::{io::prelude::*, sync::Mutex};
fn main() -> anyhow::Result<()> {
let listener = std::net::TcpListener::bind("127.0.0.1:8000")?;
let best_score_so_far = Mutex::new(u32::MAX);
std::thread::scope(|s| {
for maybe_stream in listener.incoming() {
println!("New player! Starting game...");
let Ok(stream) = maybe_stream else {
continue;
};
s.spawn(|| {
match handle_stream(stream, &best_score_so_far) {
Ok(()) => {
// cool
}
Err(e) => {
eprintln!("Stream failed: {}", e);
}
}
});
}
});
Ok(())
}
fn handle_stream(stream: std::net::TcpStream, locked_best_score_so_far: &Mutex<u32>) -> anyhow::Result<()> {
writeln!(&stream, "Hello player")?;
let game = network_green_yellow::Game::new();
// writeln!(&stream, "The solution is {:?}", game)?;
let buf_reader = std::io::BufReader::new(&stream);
let mut score = 0;
let mut line_iter = buf_reader.lines();
'guess_loop: loop {
writeln!(&stream, "Please enter your guess:")?;
let Some(line) = line_iter.next() else {
println!("Ran out of lines?");
anyhow::bail!("Ran out of input");
};
let line = line?;
writeln!(&stream, "You said: {line:?}")?;
let mut guess = [0u8; 4];
for (part, guess_digit) in line.split_ascii_whitespace().zip(guess.iter_mut()) {
let Ok(digit) = part.parse::<u8>() else {
writeln!(&stream, "I don't like that line. Try again.")?;
continue 'guess_loop;
};
if digit > 9 {
writeln!(&stream, "I don't like that line. Try again.")?;
continue 'guess_loop;
}
*guess_digit = digit;
}
writeln!(&stream, "You guessed: {:?}", guess)?;
score += 1;
let blocks = game.calc_green_and_yellow(&guess);
writeln!(&stream, "You got {}", blocks)?;
if blocks == "🟩🟩🟩🟩" {
let mut best_score_so_far = locked_best_score_so_far.lock().unwrap();
if score < *best_score_so_far {
*best_score_so_far = score;
writeln!(
&stream,
"Well done! You took {} attempts and got a new high score!",
score
)?;
} else {
writeln!(
&stream,
"Well done! You took {} attempts but that sucks. Try again.",
score
)?;
}
break;
}
}
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment