import pandas as pd import difflib from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity import gradio as gr import numpy as np import re from PIL import Image from io import BytesIO import requests # Load the data - when deploying, adjust the path to where your dataset will be stored def load_data(): try: # For Hugging Face Spaces deployment, you might need to adjust this path data = pd.read_csv('games_march2025_cleaned.csv', nrows=20000, on_bad_lines='skip', engine='python') return data except Exception as e: print(f"Error loading data: {e}") return None # Prepare the feature vectors for similarity calculation def prepare_features(data): selected_features = ['genres', 'price', 'average_playtime_2weeks', 'tags', 'average_playtime_forever'] for feature in selected_features: data[feature] = data[feature].fillna('') combined_features = ( data['genres'] + ' ' + data['price'].astype(str) + ' ' + data['average_playtime_2weeks'].astype(str) + ' ' + data['tags'].astype(str) ) vectorizer = TfidfVectorizer() feature_vectors = vectorizer.fit_transform(combined_features) return feature_vectors # Function to get game recommendations def get_recommendations(game_name, data, feature_vectors): list_of_all_titles = data['name'].tolist() find_close_match = difflib.get_close_matches(game_name, list_of_all_titles) if not find_close_match: return "No match found for the game name. Please try another title." closest_match = find_close_match[0] index_of_the_game = data.loc[data['name'] == closest_match].index[0] game_similarity = cosine_similarity(feature_vectors) similarity_scores = list(enumerate(game_similarity[index_of_the_game])) sorted_similar_games = sorted(similarity_scores, key=lambda x: x[1], reverse=True) results = [] for i, game in enumerate(sorted_similar_games[1:10], 1): # Skip the first one as it's the game itself index = game[0] name = data.loc[index, 'name'] # Get additional information about = data.loc[index, 'about_the_game'] if 'about_the_game' in data.columns else "No description available" # Try to get an image - first check screenshots, then header_image image_url = None if 'screenshots' in data.columns and pd.notna(data.loc[index, 'screenshots']): # Try to extract the first screenshot URL screenshots = data.loc[index, 'screenshots'] if isinstance(screenshots, str): # Handle potential JSON format if screenshots.startswith('[') and ']' in screenshots: try: import json screenshot_list = json.loads(screenshots) if screenshot_list and isinstance(screenshot_list, list) and len(screenshot_list) > 0: if isinstance(screenshot_list[0], dict) and 'path_full' in screenshot_list[0]: image_url = screenshot_list[0]['path_full'] elif isinstance(screenshot_list[0], str): image_url = screenshot_list[0] except: # If JSON parsing fails, try regex url_match = re.search(r'https?://[^\s,\'"]+\.(jpg|jpeg|png|gif)', screenshots) if url_match: image_url = url_match.group(0) # If no screenshot, try header image if (image_url is None or image_url == '') and 'header_image' in data.columns: image_url = data.loc[index, 'header_image'] if pd.notna(data.loc[index, 'header_image']) else None # Get platform information platforms = [] if 'windows' in data.columns and data.loc[index, 'windows'] == 1: platforms.append("Windows") if 'mac' in data.columns and data.loc[index, 'mac'] == 1: platforms.append("Mac") if 'linux' in data.columns and data.loc[index, 'linux'] == 1: platforms.append("Linux") platforms_str = ", ".join(platforms) if platforms else "Unknown" # Get price information price = data.loc[index, 'price'] if 'price' in data.columns else None price_str = f"${price}" if pd.notna(price) and price != '' else "Price not available" # Format the result result = f"**{name}**\n\n" result += f"**Price:** {price_str}\n" result += f"**Platforms:** {platforms_str}\n\n" # Add genres if available if 'genres' in data.columns and pd.notna(data.loc[index, 'genres']): genres = data.loc[index, 'genres'] if genres and genres != '': # Clean up genres format if isinstance(genres, str): # Handle potential JSON format if genres.startswith('[') and ']' in genres: try: import json genres_list = json.loads(genres) if isinstance(genres_list, list): genres = ", ".join(genres_list) except: pass result += f"**Genres:** {genres}\n\n" # Truncate and clean the about text if about and about != "": # Remove HTML tags about_clean = re.sub(r'<.*?>', '', about) about_truncated = about_clean[:300] + "..." if len(about_clean) > 300 else about_clean result += f"**About the Game:** {about_truncated}\n" else: result += "**About the Game:** No description available\n" results.append((result, image_url)) return results # Function to safely load image from URL def load_image_safely(url): if not url or str(url).lower() == 'nan': return None try: response = requests.get(url, timeout=5) if response.status_code == 200: return Image.open(BytesIO(response.content)) else: return None except: return None # Gradio interface function def recommend_games(game_name): data = load_data() if data is None: return "Failed to load data. Please check the data file." feature_vectors = prepare_features(data) recommendations = get_recommendations(game_name, data, feature_vectors) if isinstance(recommendations, str): return recommendations # Format the output for Gradio result_texts = [] result_images = [] for result, image_url in recommendations: result_texts.append(result) # Add similarity score if available if image_url and str(image_url) != 'nan': # For Hugging Face Spaces, use the URL directly # The image loading will happen through the browser result_images.append(image_url) else: # Use a placeholder image if no image URL is available result_images.append(None) # Return list of recommendations with their info return result_texts, result_images # Create the Gradio interface with individual game cards def create_recommendation_ui(game_name): data = load_data() if data is None: return [gr.Markdown("Failed to load data. Please check the data file.")] feature_vectors = prepare_features(data) recommendations = get_recommendations(game_name, data, feature_vectors) if isinstance(recommendations, str): return [gr.Markdown(recommendations)] result_texts, result_images = recommendations # Create output components dynamically output_components = [] for i, (text, img_url) in enumerate(zip(result_texts, result_images)): with gr.Group(): with gr.Row(): with gr.Column(scale=1): if img_url and str(img_url) != 'nan': output_components.append(gr.Image(value=img_url, label=f"Game {i+1}")) else: output_components.append(gr.Markdown("*No image available*")) with gr.Column(scale=2): output_components.append(gr.Markdown(text)) output_components.append(gr.Markdown("---")) return output_components with gr.Blocks(title="Steam Game Recommender") as demo: gr.Markdown("# Steam Game Recommender") gr.Markdown("Enter your favorite game to get recommendations for similar games.") with gr.Row(): input_text = gr.Textbox(label="Enter your favorite game:", placeholder="e.g., Half-Life 2") submit_btn = gr.Button("Get Recommendations", variant="primary") output_container = gr.Group(visible=False) with output_container: gr.Markdown("## Your Recommendations") recommendation_outputs = [] for i in range(9): # For 9 recommendations with gr.Group(): with gr.Row(): with gr.Column(scale=1): recommendation_outputs.append(gr.Image(label=f"Game {i+1}")) with gr.Column(scale=2): recommendation_outputs.append(gr.Markdown()) recommendation_outputs.append(gr.Markdown("---")) def process_recommendations(game_name): data = load_data() if data is None: return [gr.update(visible=True), gr.update(value="Failed to load data. Please check the data file.")] feature_vectors = prepare_features(data) recommendations = get_recommendations(game_name, data, feature_vectors) if isinstance(recommendations, str): return [gr.update(visible=True), gr.update(value=recommendations)] result_texts, result_images = recommendations updates = [gr.update(visible=True)] for i, (text, img_url) in enumerate(zip(result_texts, result_images)): updates.append(gr.update(value=img_url if img_url and str(img_url) != 'nan' else None)) updates.append(gr.update(value=text)) updates.append(gr.update()) # Fill any remaining slots with empty updates while len(updates) < len(recommendation_outputs) + 1: updates.append(gr.update(visible=False)) return updates submit_btn.click( fn=process_recommendations, inputs=input_text, outputs=[output_container] + recommendation_outputs ) # Launch the app if __name__ == "__main__": demo.launch()