use rand::Rng;
use std::io::{self, Write};
use std::thread;
use std::time::Duration;
use vexus::{Activation, NeuralNetwork, Sigmoid};
const MODEL_FILE_1: &str = "rps_model_1.json";
const MODEL_FILE_2: &str = "rps_model_2.json";
const HISTORY_LENGTH: usize = 32; const TRAINING_ITERATIONS: usize = 10; const AI_VS_AI_GAMES: usize = 2000; const AI_VS_AI_DELAY_MS: u64 = 0;
#[derive(Debug, PartialEq, Clone, Copy)]
enum Move {
Rock = 0,
Paper = 1,
Scissors = 2,
}
impl Move {
fn from_str(s: &str) -> Option<Move> {
match s.to_lowercase().as_str() {
"r" | "rock" => Some(Move::Rock),
"p" | "paper" => Some(Move::Paper),
"s" | "scissors" => Some(Move::Scissors),
_ => None,
}
}
fn from_index(idx: usize) -> Move {
match idx % 3 {
0 => Move::Rock,
1 => Move::Paper,
2 => Move::Scissors,
_ => unreachable!(),
}
}
fn beats(&self, other: &Move) -> bool {
match (self, other) {
(Move::Rock, Move::Scissors) => true,
(Move::Paper, Move::Rock) => true,
(Move::Scissors, Move::Paper) => true,
_ => false,
}
}
fn random() -> Move {
let mut rng = rand::rng();
match rng.random_range(0..3) {
0 => Move::Rock,
1 => Move::Paper,
_ => Move::Scissors,
}
}
fn counter(&self) -> Move {
match self {
Move::Rock => Move::Paper,
Move::Paper => Move::Scissors,
Move::Scissors => Move::Rock,
}
}
fn to_string(&self) -> &'static str {
match self {
Move::Rock => "Rock",
Move::Paper => "Paper",
Move::Scissors => "Scissors",
}
}
fn to_input_vec(&self) -> Vec<f32> {
let mut result = vec![0.0, 0.0, 0.0];
result[*self as usize] = 1.0;
result
}
}
struct MovePredictor {
nn: NeuralNetwork,
player_history: Vec<Move>,
initialized: bool,
model_file: String,
}
impl MovePredictor {
fn new(model_file: &str) -> Self {
let nn = NeuralNetwork::new(
vec![HISTORY_LENGTH * 3, 32, 32, 16, 3],
0.1,
Box::new(Sigmoid),
);
MovePredictor {
nn,
player_history: Vec::new(),
initialized: false,
model_file: model_file.to_string(),
}
}
fn record_move(&mut self, player_move: Move) {
self.player_history.push(player_move);
if self.player_history.len() > HISTORY_LENGTH * 2 {
self.player_history =
self.player_history[self.player_history.len() - HISTORY_LENGTH * 2..].to_vec();
}
if self.player_history.len() >= HISTORY_LENGTH {
self.initialized = true;
}
}
fn train(&mut self) {
if !self.initialized || self.player_history.len() < HISTORY_LENGTH + 1 {
return;
}
for i in 0..self.player_history.len() - HISTORY_LENGTH {
let inputs = self.history_to_input(&self.player_history[i..i + HISTORY_LENGTH]);
let target = self.player_history[i + HISTORY_LENGTH].to_input_vec();
for _ in 0..TRAINING_ITERATIONS {
let outputs = self.nn.forward(&inputs);
self.nn.backpropagate(&target);
}
}
}
fn predict_next_move(&mut self) -> Move {
if !self.initialized || self.player_history.len() < HISTORY_LENGTH {
return Move::random();
}
let recent_history = &self.player_history[self.player_history.len() - HISTORY_LENGTH..];
let inputs = self.history_to_input(recent_history);
let outputs = self.nn.forward(&inputs);
let mut max_idx = 0;
let mut max_val = outputs[0];
for (i, &val) in outputs.iter().enumerate().skip(1) {
if val > max_val {
max_val = val;
max_idx = i;
}
}
Move::from_index(max_idx)
}
fn make_move(&mut self) -> Move {
if !self.initialized || self.player_history.len() < HISTORY_LENGTH {
return Move::random();
}
let mut rng = rand::rng();
if rng.random_bool(0.2) {
return Move::random();
}
let predicted_next = self.predict_next_move();
predicted_next.counter()
}
fn history_to_input(&self, history: &[Move]) -> Vec<f32> {
let mut inputs = Vec::with_capacity(history.len() * 3);
for &m in history {
inputs.extend_from_slice(&m.to_input_vec());
}
inputs
}
}
enum GameMode {
PlayerVsAI,
AIVsAI,
}
fn get_game_mode() -> GameMode {
loop {
println!("=== ROCK PAPER SCISSORS with AI ===");
println!("Select game mode:");
println!("1. Player vs AI");
println!("2. AI vs AI");
print!("Enter your choice (1-2): ");
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
match input.trim() {
"1" => return GameMode::PlayerVsAI,
"2" => return GameMode::AIVsAI,
_ => {
println!("Invalid choice! Please enter 1 or 2.");
println!();
}
}
}
}
fn player_vs_ai_mode() {
println!("\n=== PLAYER VS AI MODE ===");
println!("Enter 'q' to quit at any time");
let mut player_score = 0;
let mut computer_score = 0;
let mut predictor = MovePredictor::new(MODEL_FILE_1);
loop {
print!("Enter your move (r)ock, (p)aper, (s)cissors: ");
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
let input = input.trim();
if input == "q" || input == "quit" {
break;
}
let player_move = match Move::from_str(input) {
Some(m) => m,
None => {
println!("Invalid move! Please enter 'r', 'p', or 's'.");
continue;
}
};
let predicted_move = predictor.predict_next_move();
let computer_move = predicted_move.counter();
predictor.record_move(player_move);
predictor.train();
println!("You chose: {}", player_move.to_string());
println!("Computer chose: {}", computer_move.to_string());
if player_move == computer_move {
println!("It's a tie!");
} else if player_move.beats(&computer_move) {
println!("You win this round!");
player_score += 1;
} else {
println!("Computer wins this round!");
computer_score += 1;
}
println!(
"Score - You: {}, Computer: {}",
player_score, computer_score
);
println!();
}
println!("\nFinal Score:");
println!("You: {}", player_score);
println!("Computer: {}", computer_score);
if player_score > computer_score {
println!("Congratulations! You won the game!");
} else if player_score < computer_score {
println!("Better luck next time! Computer won the game.");
} else {
println!("It's a tie game!");
}
}
fn ai_vs_ai_mode() {
println!("\n=== AI VS AI MODE ===");
println!(
"The AIs will play {} games against each other",
AI_VS_AI_GAMES
);
println!("Press Ctrl+C to stop at any time");
println!();
let mut ai1 = MovePredictor::new(MODEL_FILE_1);
let mut ai2 = MovePredictor::new(MODEL_FILE_2);
let mut ai1_score = 0;
let mut ai2_score = 0;
let mut ties = 0;
println!("Game starting...");
for _ in 0..HISTORY_LENGTH {
let random_move = Move::random();
ai1.record_move(random_move);
ai2.record_move(random_move);
}
for game in 1..=AI_VS_AI_GAMES {
let ai1_move = ai1.make_move();
let ai2_move = ai2.make_move();
ai1.record_move(ai2_move);
ai2.record_move(ai1_move);
ai1.train();
ai2.train();
if game % 100 == 0 {
println!(
"Game {}: AI1 chose {}, AI2 chose {}",
game,
ai1_move.to_string(),
ai2_move.to_string()
);
}
if ai1_move == ai2_move {
ties += 1;
} else if ai1_move.beats(&ai2_move) {
ai1_score += 1;
} else {
ai2_score += 1;
}
thread::sleep(Duration::from_millis(AI_VS_AI_DELAY_MS));
}
println!("\nFinal Score after {} games:", AI_VS_AI_GAMES);
println!("AI1: {}", ai1_score);
println!("AI2: {}", ai2_score);
println!("Ties: {}", ties);
if ai1_score > ai2_score {
println!("AI1 won the tournament!");
} else if ai1_score < ai2_score {
println!("AI2 won the tournament!");
} else {
println!("The tournament ended in a tie!");
}
}
fn main() {
let game_mode = get_game_mode();
match game_mode {
GameMode::PlayerVsAI => player_vs_ai_mode(),
GameMode::AIVsAI => ai_vs_ai_mode(),
}
}