library(shiny) library(shinyjs) # Simple configuration CONFIG <- list( tile_size = 150, shuffle_moves = 100 ) source("minimal_global.R") # UI ui <- fluidPage( useShinyjs(), titlePanel(title = NULL, windowTitle = "Puzzle8"), tags$head( tags$meta(name = "viewport", content = "width=device-width, initial-scale=1"), tags$style(HTML(" :root { --tile-size: 150px; --primary-color: #2196F3; --success-color: #4CAF50; --warning-color: #FF9800; --border-radius: 8px; --shadow-light: 0 2px 4px rgba(0,0,0,0.1); --shadow-medium: 0 4px 8px rgba(0,0,0,0.15); --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; padding: 20px 0; } .container-fluid { max-width: 800px; margin: 0 auto; } .game-container { background: rgba(255, 255, 255, 0.95); border-radius: 16px; box-shadow: 0 20px 40px rgba(0,0,0,0.1); backdrop-filter: blur(10px); padding: 30px; margin: 20px; text-align: center; } .puzzle-grid { display: grid; grid-template-columns: repeat(3, var(--tile-size)); grid-template-rows: repeat(3, var(--tile-size)); gap: 8px; justify-content: center; margin: 30px auto; padding: 20px; background: rgba(0,0,0,0.05); border-radius: var(--border-radius); } .puzzle-tile { width: var(--tile-size); height: var(--tile-size); border: none; border-radius: var(--border-radius); background: linear-gradient(145deg, var(--primary-color), #1976D2); color: white; font-size: 32px; font-weight: 700; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: var(--transition); box-shadow: var(--shadow-light); position: relative; overflow: hidden; } .puzzle-tile::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); transition: left 0.5s; } .puzzle-tile:hover::before { left: 100%; } .puzzle-tile:hover { transform: translateY(-2px) scale(1.02); box-shadow: var(--shadow-medium); } .puzzle-tile:active { transform: translateY(0) scale(0.98); } .empty-tile { background: rgba(255,255,255,0.8); border: 2px dashed #ccc; box-shadow: inset 0 2px 4px rgba(0,0,0,0.1); } .moves-counter { background: white; padding: 20px; border-radius: var(--border-radius); margin: 20px 0; box-shadow: var(--shadow-light); border-left: 4px solid var(--primary-color); } .congratulations-card { background: linear-gradient(145deg, #4CAF50, #388E3C); color: white; padding: 25px; border-radius: var(--border-radius); margin: 20px 0; box-shadow: var(--shadow-medium); animation: celebration 0.6s ease; border: none; } @keyframes celebration { 0% { opacity: 0; transform: translateY(-20px) scale(0.9); } 50% { transform: translateY(-5px) scale(1.05); } 100% { opacity: 1; transform: translateY(0) scale(1); } } .moves-value { font-size: 36px; font-weight: bold; color: var(--primary-color); margin-bottom: 5px; } .moves-label { font-size: 14px; color: #666; text-transform: uppercase; letter-spacing: 1px; } .congratulations-title { font-size: 28px; font-weight: bold; margin-bottom: 10px; display: flex; align-items: center; justify-content: center; gap: 10px; } .congratulations-subtitle { font-size: 18px; font-weight: 600; opacity: 0.9; } .btn-game { padding: 15px 30px; border: none; border-radius: var(--border-radius); font-weight: 600; font-size: 16px; cursor: pointer; transition: var(--transition); text-transform: uppercase; letter-spacing: 0.5px; margin: 10px; min-width: 140px; } .btn-success { background: linear-gradient(145deg, var(--success-color), #388E3C); color: white; } .btn-warning { background: linear-gradient(145deg, var(--warning-color), #F57C00); color: white; } .btn-game:hover { transform: translateY(-2px); box-shadow: var(--shadow-medium); } .victory-message { background: linear-gradient(145deg, #4CAF50, #388E3C); color: white; padding: 25px; border-radius: var(--border-radius); margin: 20px 0; font-size: 18px; font-weight: 600; box-shadow: var(--shadow-medium); animation: slideIn 0.5s ease; } @keyframes slideIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } .instructions { background: white; border-radius: var(--border-radius); padding: 25px; box-shadow: var(--shadow-light); text-align: left; margin: 20px 0; } .instructions h3 { color: var(--primary-color); margin-bottom: 15px; font-weight: 600; text-align: center; } .instructions ul { padding-left: 20px; } .instructions li { margin-bottom: 8px; line-height: 1.5; } .goal-grid { display: grid; grid-template-columns: repeat(3, 50px); gap: 5px; justify-content: center; margin: 15px 0; } .goal-tile { width: 50px; height: 50px; background: var(--primary-color); color: white; display: flex; align-items: center; justify-content: center; border-radius: 4px; font-weight: bold; font-size: 16px; } .goal-empty { background: #f0f0f0; border: 2px dashed #ccc; } /* Responsive Design */ @media (max-width: 768px) { :root { --tile-size: 100px; } .game-container { margin: 10px; padding: 20px; } .puzzle-tile { font-size: 24px; } .moves-value { font-size: 28px; } .congratulations-title { font-size: 24px; } .congratulations-subtitle { font-size: 16px; } .btn-game { min-width: auto; padding: 12px 20px; font-size: 14px; } } @media (max-width: 480px) { :root { --tile-size: 80px; } .puzzle-tile { font-size: 20px; } .moves-value { font-size: 24px; } .congratulations-title { font-size: 20px; } .congratulations-subtitle { font-size: 14px; } } ")) ), div(class = "container-fluid", div(class = "game-container", # Title h1("🧩 Puzzle8", style = "background: linear-gradient(145deg, #2196F3, #1976D2); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 800; font-size: 3rem; margin-bottom: 30px;"), # Dynamic Counter/Congratulations Card uiOutput("status_card"), # Puzzle Grid div(class = "puzzle-grid", lapply(1:9, function(i) { div(id = paste0("tile_container_", i), uiOutput(paste0("tile_", i))) }) ), # Control Buttons div(class = "text-center", actionButton("new_game", "New Game", class = "btn-game btn-success"), actionButton("reset_game", "Reset", class = "btn-game btn-warning") ), # Instructions div(class = "instructions", h3("🎯 How to Play"), p("Arrange numbers 1-8 in order with the empty space in the bottom-right corner."), tags$ul( tags$li("Click on tiles adjacent to the empty space to move them"), tags$li("Only tiles next to the empty space can be moved"), tags$li("Try to solve the puzzle in as few moves as possible!") ), div(style = "text-align: center; margin-top: 20px;", h4("🏆 Goal:"), div(class = "goal-grid", lapply(1:8, function(i) { div(class = "goal-tile", i) }), div(class = "goal-tile goal-empty") ) ) ) ) ) ) # Server server <- function(input, output, session) { # Simple game state game_state <- reactiveValues( puzzle = NULL, initial_puzzle = NULL, moves = 0, game_won = FALSE ) # Initialize game observe({ if(is.null(game_state$puzzle)) { new_puzzle <- create_solvable_puzzle() game_state$puzzle <- new_puzzle game_state$initial_puzzle <- new_puzzle } }) # Tile click handlers observe({ lapply(1:9, function(i) { observeEvent(input[[paste0("tile_", i)]], { if(!game_state$game_won && !is.null(game_state$puzzle)) { result <- move_tile(game_state$puzzle, i) if(result$moved) { game_state$puzzle <- result$puzzle game_state$moves <- game_state$moves + 1 # Check win condition if(is_solved(game_state$puzzle)) { game_state$game_won <- TRUE # showNotification( # paste("🎉 Congratulations! Solved in", game_state$moves, "moves!"), # type = "message", duration = 5 # ) } } } }) }) }) # New Game button observeEvent(input$new_game, { new_puzzle <- create_solvable_puzzle() game_state$puzzle <- new_puzzle game_state$initial_puzzle <- new_puzzle game_state$moves <- 0 game_state$game_won <- FALSE # showNotification("New game started!", type = "message") }) # Reset button observeEvent(input$reset_game, { if(!is.null(game_state$initial_puzzle)) { game_state$puzzle <- game_state$initial_puzzle game_state$moves <- 0 game_state$game_won <- FALSE # showNotification("Game reset!", type = "message") } }) # Render tiles lapply(1:9, function(i) { output[[paste0("tile_", i)]] <- renderUI({ req(game_state$puzzle) value <- game_state$puzzle[i] if(value == 0) { div(class = "empty-tile") } else { actionButton( paste0("tile_", i), label = as.character(value), class = "puzzle-tile" ) } }) }) # Dynamic Status Card (Moves Counter or Congratulations) output$status_card <- renderUI({ if(game_state$game_won) { # Congratulations Card div(class = "congratulations-card", div(class = "congratulations-title", "🏆 Congratulations!" ), div(class = "congratulations-subtitle", paste("Puzzle solved in", game_state$moves, "moves!") ) ) } else { # Moves Counter Card div(class = "moves-counter", div(class = "moves-value", game_state$moves), div(class = "moves-label", "Moves") ) } }) } # Run the application shinyApp(ui = ui, server = server)