RPS / app.py
resberry's picture
Update app.py
13c8825 verified
import os
import random
import csv
import numpy as np
import tensorflow as tf
import gradio as gr
from tensorflow.keras.models import Sequential, load_model
from tensorflow.keras.layers import LSTM, Dense, Embedding
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.preprocessing.text import Tokenizer
# Disable GPU for Hugging Face Spaces
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
os.environ["TF_ENABLE_ONEDNN_OPTS"] = "0"
# Reduce TensorFlow verbosity
tf.get_logger().setLevel('ERROR')
# File paths
csv_filename = "game_moves.csv"
model_filename = "lstm_model.h5"
# Mapping choices to numerical values
choices = {'rock': 0, 'paper': 1, 'scissors': 2}
rev_choices = {0: 'rock', 1: 'paper', 2: 'scissors'}
# Ensure CSV exists
if not os.path.exists(csv_filename):
with open(csv_filename, mode='w', newline='') as file:
writer = csv.writer(file)
writer.writerow(["Player Choice", "Computer Choice", "Result"])
def load_data():
""" Loads past player moves from CSV file. """
try:
with open(csv_filename, mode="r") as file:
reader = csv.reader(file)
next(reader) # Skip header
return [row[0] for row in reader if row] # Added check for empty rows
except FileNotFoundError:
return []
def train_lstm_model(data):
""" Trains an LSTM model to predict the player's next move. """
if len(data) < 6:
return None # Not enough data for meaningful training
# Tokenizer only needs to work with our 3 choices
tokenizer = Tokenizer(num_words=3)
tokenizer.fit_on_texts(["rock", "paper", "scissors"]) # Fit on all possible choices
sequences = tokenizer.texts_to_sequences(data)
# Create sequences for training
X, y = [], []
for i in range(len(sequences) - 5):
X.append(sequences[i:i+5])
y.append(sequences[i+5][0] if sequences[i+5] else 0)
if len(X) == 0:
return None
X = pad_sequences(X, maxlen=5)
y = np.array(y)
model = Sequential([
Embedding(input_dim=4, output_dim=10, input_length=5), # input_dim=4 (0-3)
LSTM(30, return_sequences=False),
Dense(3, activation="softmax")
])
model.compile(loss="sparse_categorical_crossentropy",
optimizer="adam",
metrics=["accuracy"])
model.fit(X, y, epochs=10, batch_size=1, verbose=0)
model.save(model_filename)
return model
def get_computer_choice(model, past_moves):
""" Predicts player's next move and counteracts it. """
if len(past_moves) < 5 or model is None:
return random.choice(["rock", "paper", "scissors"])
try:
# Prepare input data for prediction
tokenizer = Tokenizer(num_words=3)
tokenizer.fit_on_texts(["rock", "paper", "scissors"])
sequences = tokenizer.texts_to_sequences(past_moves[-5:])
if len(sequences) < 5:
return random.choice(["rock", "paper", "scissors"])
sequence = pad_sequences([sequences], maxlen=5)
prediction = model.predict(sequence, verbose=0)
predicted_choice = rev_choices[np.argmax(prediction)]
# Counteract the predicted choice
counter_choices = {'rock': 'paper', 'paper': 'scissors', 'scissors': 'rock'}
return counter_choices[predicted_choice]
except:
return random.choice(["rock", "paper", "scissors"])
def get_winner(player, computer):
""" Determines the winner of the game. """
if player == computer:
return "It's a tie!"
elif (player == "rock" and computer == "scissors") or \
(player == "scissors" and computer == "paper") or \
(player == "paper" and computer == "rock"):
return "You win!"
else:
return "Computer wins!"
def save_move(player, computer, result):
""" Saves game move to CSV file. """
with open(csv_filename, mode="a", newline="") as file:
writer = csv.writer(file)
writer.writerow([player, computer, result])
# Initialize data and model
past_moves = load_data()
# Try to load existing model, otherwise create new one
if os.path.exists(model_filename):
try:
model = load_model(model_filename)
except:
model = train_lstm_model(past_moves) if len(past_moves) >= 6 else None
else:
model = train_lstm_model(past_moves) if len(past_moves) >= 6 else None
def play_game(player_choice):
""" Handles the game logic and returns the result. """
global past_moves, model
if player_choice not in choices:
return "Invalid choice. Choose rock, paper, or scissors."
# Get computer choice
if model is None:
computer_choice = random.choice(["rock", "paper", "scissors"])
else:
computer_choice = get_computer_choice(model, past_moves)
result = get_winner(player_choice, computer_choice)
# Save the move
save_move(player_choice, computer_choice, result)
past_moves.append(player_choice)
# Retrain model every 10 moves for efficiency
if len(past_moves) >= 6 and len(past_moves) % 10 == 0:
model = train_lstm_model(past_moves)
return f"**Your choice:** {player_choice}\n\n" \
f"**Computer choice:** {computer_choice}\n\n" \
f"**Result:** {result}\n\n" \
f"*Total games played: {len(past_moves)}*"
# Create Gradio interface
with gr.Blocks(title="Rock Paper Scissors AI", theme=gr.themes.Soft()) as demo:
gr.Markdown("# 🪨 📄 ✂️ Rock Paper Scissors AI")
gr.Markdown("Play against an AI that learns from your moves and tries to beat you!")
with gr.Row():
with gr.Column(scale=1):
move_input = gr.Radio(
choices=["rock", "paper", "scissors"],
label="Choose your move",
value="rock"
)
submit_btn = gr.Button("Play!", variant="primary")
with gr.Column(scale=2):
output = gr.Markdown("## Game will start here...")
submit_btn.click(
fn=play_game,
inputs=move_input,
outputs=output
)
gr.Markdown("### How it works:")
gr.Markdown("""
1. The AI uses an LSTM neural network to learn from your move patterns
2. It predicts your next move based on your last 5 moves
3. It counters your predicted move to try to win
4. The model improves as you play more games
""")
if __name__ == "__main__":
demo.launch(debug=False, show_error=True)