aephiday commited on
Commit
05ba80f
·
verified ·
1 Parent(s): 7dc32e0

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +529 -48
app.R CHANGED
@@ -1,58 +1,539 @@
1
  library(shiny)
2
- library(bslib)
3
- library(dplyr)
4
- library(ggplot2)
5
-
6
- df <- readr::read_csv("penguins.csv")
7
- # Find subset of columns that are suitable for scatter plot
8
- df_num <- df |> select(where(is.numeric), -Year)
9
-
10
- ui <- page_sidebar(
11
- theme = bs_theme(bootswatch = "minty"),
12
- title = "Penguins explorer",
13
- sidebar = sidebar(
14
- varSelectInput("xvar", "X variable", df_num, selected = "Bill Length (mm)"),
15
- varSelectInput("yvar", "Y variable", df_num, selected = "Bill Depth (mm)"),
16
- checkboxGroupInput("species", "Filter by species",
17
- choices = unique(df$Species), selected = unique(df$Species)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  ),
19
- hr(), # Add a horizontal rule
20
- checkboxInput("by_species", "Show species", TRUE),
21
- checkboxInput("show_margins", "Show marginal plots", TRUE),
22
- checkboxInput("smooth", "Add smoother"),
 
 
 
 
 
 
 
 
 
 
 
23
  ),
24
- plotOutput("scatter")
25
  )
26
 
 
27
  server <- function(input, output, session) {
28
- subsetted <- reactive({
29
- req(input$species)
30
- df |> filter(Species %in% input$species)
31
- })
32
-
33
- output$scatter <- renderPlot(
34
- {
35
- p <- ggplot(subsetted(), aes(!!input$xvar, !!input$yvar)) +
36
- theme_light() +
37
- list(
38
- theme(legend.position = "bottom"),
39
- if (input$by_species) aes(color = Species),
40
- geom_point(),
41
- if (input$smooth) geom_smooth()
42
- )
43
-
44
- if (input$show_margins) {
45
- margin_type <- if (input$by_species) "density" else "histogram"
46
- p <- p |> ggExtra::ggMarginal(
47
- type = margin_type, margins = "both",
48
- size = 8, groupColour = input$by_species, groupFill = input$by_species
49
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
- p
53
- },
54
- res = 100
55
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  }
57
 
58
- shinyApp(ui, server)
 
 
1
  library(shiny)
2
+ library(shinydashboard)
3
+
4
+ max_hint <- 3
5
+ size <- 200
6
+
7
+ # Fungsi untuk membuat puzzle awal yang dapat diselesaikan
8
+ create_solvable_puzzle <- function() {
9
+ # Puzzle solved
10
+ solved <- c(1, 2, 3, 4, 5, 6, 7, 8, 0)
11
+
12
+ # Acak puzzle dengan langkah-langkah valid
13
+ puzzle <- solved
14
+ moves <- 0
15
+ blank_pos <- 9
16
+
17
+ while(moves < 100) {
18
+ # Tentukan kemungkinan gerakan
19
+ possible_moves <- c()
20
+ if(blank_pos %% 3 != 1) possible_moves <- c(possible_moves, blank_pos - 1) # Kiri
21
+ if(blank_pos %% 3 != 0) possible_moves <- c(possible_moves, blank_pos + 1) # Kanan
22
+ if(blank_pos > 3) possible_moves <- c(possible_moves, blank_pos - 3) # Atas
23
+ if(blank_pos < 7) possible_moves <- c(possible_moves, blank_pos + 3) # Bawah
24
+
25
+ # Pilih gerakan acak
26
+ move <- sample(possible_moves, 1)
27
+
28
+ # Tukar posisi
29
+ temp <- puzzle[blank_pos]
30
+ puzzle[blank_pos] <- puzzle[move]
31
+ puzzle[move] <- temp
32
+
33
+ blank_pos <- move
34
+ moves <- moves + 1
35
+ }
36
+
37
+ return(puzzle)
38
+ }
39
+
40
+ # Fungsi untuk mengecek apakah puzzle sudah selesai
41
+ is_solved <- function(puzzle) {
42
+ target <- c(1, 2, 3, 4, 5, 6, 7, 8, 0)
43
+ return(identical(puzzle, target))
44
+ }
45
+
46
+ # Fungsi untuk mendapatkan posisi kotak kosong
47
+ get_blank_position <- function(puzzle) {
48
+ return(which(puzzle == 0))
49
+ }
50
+
51
+ # Fungsi untuk memindahkan kotak - dimodifikasi untuk return status pergerakan
52
+ move_tile <- function(puzzle, tile_position) {
53
+ blank_pos <- get_blank_position(puzzle)
54
+
55
+ # Cek apakah gerakan valid
56
+ valid_moves <- c()
57
+ if(blank_pos %% 3 != 1) valid_moves <- c(valid_moves, blank_pos - 1) # Kiri
58
+ if(blank_pos %% 3 != 0) valid_moves <- c(valid_moves, blank_pos + 1) # Kanan
59
+ if(blank_pos > 3) valid_moves <- c(valid_moves, blank_pos - 3) # Atas
60
+ if(blank_pos < 7) valid_moves <- c(valid_moves, blank_pos + 3) # Bawah
61
+
62
+ # Jika gerakan valid, lakukan pertukaran
63
+ if(tile_position %in% valid_moves) {
64
+ # Lakukan pertukaran
65
+ temp <- puzzle[blank_pos]
66
+ puzzle[blank_pos] <- puzzle[tile_position]
67
+ puzzle[tile_position] <- temp
68
+ return(list(puzzle = puzzle, moved = TRUE)) # Return dengan status moved TRUE
69
+ }
70
+
71
+ # Jika tidak valid, return puzzle tanpa perubahan dan status moved FALSE
72
+ return(list(puzzle = puzzle, moved = FALSE))
73
+ }
74
+
75
+ # Fungsi untuk menghitung jarak Manhattan antara dua posisi
76
+ manhattan_distance <- function(pos1, pos2) {
77
+ row1 <- (pos1 - 1) %/% 3 + 1
78
+ col1 <- (pos1 - 1) %% 3 + 1
79
+ row2 <- (pos2 - 1) %/% 3 + 1
80
+ col2 <- (pos2 - 1) %% 3 + 1
81
+ return(abs(row1 - row2) + abs(col1 - col2))
82
+ }
83
+
84
+ # Fungsi untuk menghitung heuristik (jumlah jarak Manhattan semua kotak ke posisi target)
85
+ calculate_heuristic <- function(puzzle) {
86
+ target_positions <- c(1, 2, 3, 4, 5, 6, 7, 8, 9) # posisi target untuk angka 1-8, 0 di posisi 9
87
+ heuristic <- 0
88
+
89
+ for(i in 1:8) {
90
+ current_pos <- which(puzzle == i)
91
+ target_pos <- i
92
+ heuristic <- heuristic + manhattan_distance(current_pos, target_pos)
93
+ }
94
+
95
+ return(heuristic)
96
+ }
97
+
98
+ # Fungsi A* untuk mencari solusi optimal
99
+ solve_puzzle <- function(initial_puzzle) {
100
+ # Queue untuk menyimpan state yang akan dieksplorasi
101
+ # Setiap elemen: list(puzzle = ..., cost = ..., path = ...)
102
+ queue <- list(list(puzzle = initial_puzzle, cost = 0, path = c()))
103
+
104
+ # Set untuk menyimpan state yang sudah dikunjungi
105
+ visited <- list()
106
+
107
+ # Target state
108
+ target <- c(1, 2, 3, 4, 5, 6, 7, 8, 0)
109
+
110
+ while(length(queue) > 0) {
111
+ # Temukan state dengan cost terkecil
112
+ costs <- sapply(queue, function(x) x$cost)
113
+ min_index <- which.min(costs)[1]
114
+ current <- queue[[min_index]]
115
+
116
+ # Hapus dari queue
117
+ queue <- queue[-min_index]
118
+
119
+ # Cek apakah sudah mencapai target
120
+ if(identical(current$puzzle, target)) {
121
+ return(current$path)
122
+ }
123
+
124
+ # Buat key untuk state ini
125
+ state_key <- paste(current$puzzle, collapse = ",")
126
+
127
+ # Jika sudah dikunjungi, lewati
128
+ if(state_key %in% visited) {
129
+ next
130
+ }
131
+
132
+ # Tandai sebagai sudah dikunjungi
133
+ visited <- c(visited, state_key)
134
+
135
+ # Dapatkan posisi blank
136
+ blank_pos <- get_blank_position(current$puzzle)
137
+
138
+ # Dapatkan semua gerakan valid
139
+ valid_moves <- c()
140
+ if(blank_pos %% 3 != 1) valid_moves <- c(valid_moves, blank_pos - 1) # Kiri
141
+ if(blank_pos %% 3 != 0) valid_moves <- c(valid_moves, blank_pos + 1) # Kanan
142
+ if(blank_pos > 3) valid_moves <- c(valid_moves, blank_pos - 3) # Atas
143
+ if(blank_pos < 7) valid_moves <- c(valid_moves, blank_pos + 3) # Bawah
144
+
145
+ # Untuk setiap gerakan valid
146
+ for(move in valid_moves) {
147
+ # Buat salinan puzzle dan coba gerakan
148
+ new_puzzle <- current$puzzle
149
+ temp <- new_puzzle[blank_pos]
150
+ new_puzzle[blank_pos] <- new_puzzle[move]
151
+ new_puzzle[move] <- temp
152
+
153
+ # Buat key untuk state baru
154
+ new_state_key <- paste(new_puzzle, collapse = ",")
155
+
156
+ # Jika belum dikunjungi
157
+ if(!(new_state_key %in% visited)) {
158
+ # Hitung heuristik
159
+ heuristic <- calculate_heuristic(new_puzzle)
160
+ # Hitung cost (langkah + heuristik)
161
+ new_cost <- length(current$path) + 1 + heuristic
162
+
163
+ # Tambahkan ke queue
164
+ new_path <- c(current$path, move)
165
+ queue <- c(queue, list(list(puzzle = new_puzzle, cost = new_cost, path = new_path)))
166
+ }
167
+ }
168
+ }
169
+
170
+ # Jika tidak ditemukan solusi
171
+ return(NULL)
172
+ }
173
+
174
+ # Fungsi untuk mendapatkan hint berdasarkan solusi yang disimpan
175
+ get_hint_from_solution <- function(puzzle, solution, current_step) {
176
+ # Jika solusi tidak tersedia atau sudah selesai
177
+ if(is.null(solution) || current_step >= length(solution)) {
178
+ return(NULL)
179
+ }
180
+
181
+ # Kembalikan langkah berikutnya dalam solusi
182
+ return(solution[current_step + 1])
183
+ }
184
+
185
+
186
+ # Fungsi BFS untuk mencari solusi optimal
187
+ solve_puzzle_bfs <- function(initial_puzzle) {
188
+ # Jika sudah solved, return 0
189
+ if(is_solved(initial_puzzle)) {
190
+ return(0)
191
+ }
192
+
193
+ # Queue untuk menyimpan state yang akan dieksplorasi
194
+ # Setiap elemen: list(puzzle = ..., depth = ...)
195
+ queue <- list(list(puzzle = initial_puzzle, depth = 0))
196
+
197
+ # Set untuk menyimpan state yang sudah dikunjungi
198
+ visited <- c()
199
+ visited[[paste(initial_puzzle, collapse = ",")]] <- TRUE
200
+
201
+ while(length(queue) > 0) {
202
+ # Ambil elemen pertama dari queue
203
+ current <- queue[[1]]
204
+ queue <- queue[-1]
205
+
206
+ # Dapatkan posisi blank
207
+ blank_pos <- get_blank_position(current$puzzle)
208
+
209
+ # Dapatkan semua gerakan valid
210
+ valid_moves <- c()
211
+ if(blank_pos %% 3 != 1) valid_moves <- c(valid_moves, blank_pos - 1) # Kiri
212
+ if(blank_pos %% 3 != 0) valid_moves <- c(valid_moves, blank_pos + 1) # Kanan
213
+ if(blank_pos > 3) valid_moves <- c(valid_moves, blank_pos - 3) # Atas
214
+ if(blank_pos < 7) valid_moves <- c(valid_moves, blank_pos + 3) # Bawah
215
+
216
+ # Untuk setiap gerakan valid
217
+ for(move in valid_moves) {
218
+ # Buat salinan puzzle dan coba gerakan
219
+ new_puzzle <- current$puzzle
220
+ temp <- new_puzzle[blank_pos]
221
+ new_puzzle[blank_pos] <- new_puzzle[move]
222
+ new_puzzle[move] <- temp
223
+
224
+ # Cek apakah sudah mencapai target
225
+ if(is_solved(new_puzzle)) {
226
+ return(current$depth + 1)
227
+ }
228
+
229
+ # Buat key untuk state baru
230
+ state_key <- paste(new_puzzle, collapse = ",")
231
+
232
+ # Jika belum dikunjungi
233
+ if(is.null(visited[[state_key]])) {
234
+ # Tandai sebagai sudah dikunjungi
235
+ visited[[state_key]] <- TRUE
236
+
237
+ # Tambahkan ke queue
238
+ queue <- c(queue, list(list(puzzle = new_puzzle, depth = current$depth + 1)))
239
+ }
240
+ }
241
+ }
242
+
243
+ # Jika tidak ditemukan solusi (seharusnya tidak terjadi untuk puzzle yang solvable)
244
+ return(-1)
245
+ }
246
+
247
+
248
+ # UI
249
+ ui <- fluidPage(
250
+ titlePanel(h1("Puzzle8", style='font-weight: bold;')),
251
+
252
+ fluidRow(
253
+ column(5,
254
+ wellPanel(
255
+ tags$table(
256
+ style = "margin: 0 auto; border-collapse: separate; border-spacing: 25px 0; text-align: center;",
257
+ tags$tr(
258
+ tags$td("Langkah"),
259
+ tags$td(""),
260
+ tags$td("Hint Digunakan"),
261
+ ),
262
+ tags$tr(
263
+ tags$td(textOutput("move_count")),
264
+ tags$td(""),
265
+ tags$td(textOutput("hint_count"))
266
+ )
267
+ ),
268
+ # Grid 3x3 menggunakan table untuk memastikan layout yang benar
269
+ tags$table(
270
+ style = "margin: 0 auto; border-collapse: separate; border-spacing: 5px;",
271
+ tags$tr(
272
+ tags$td(uiOutput("tile_1")),
273
+ tags$td(uiOutput("tile_2")),
274
+ tags$td(uiOutput("tile_3"))
275
+ ),
276
+ tags$tr(
277
+ tags$td(uiOutput("tile_4")),
278
+ tags$td(uiOutput("tile_5")),
279
+ tags$td(uiOutput("tile_6"))
280
+ ),
281
+ tags$tr(
282
+ tags$td(uiOutput("tile_7")),
283
+ tags$td(uiOutput("tile_8")),
284
+ tags$td(uiOutput("tile_9"))
285
+ ),
286
+ tags$tr(
287
+ tags$td(""),
288
+ tags$td(""),
289
+ tags$td(""),
290
+ ),
291
+ tags$tr(
292
+ tags$td(actionButton("new_game", "New Game", class = "btn-success btn-block")),
293
+ tags$td(actionButton("reset_game", "Reset", class = "btn-warning btn-block")),
294
+ tags$td(actionButton("hint_button", "Hint", class = "btn-info btn-block"))
295
+ )
296
+ ),
297
+ br(),
298
+ uiOutput('game_state'),
299
+ uiOutput("final_moves_count"),
300
+ br(),
301
+ uiOutput('hint_message')
302
+ )
303
  ),
304
+ column(7,
305
+ wellPanel(
306
+ h3("Cara Bermain"),
307
+ p("Tujuan: Susun angka 1-8 secara berurutan dengan kotak kosong di pojok kanan bawah."),
308
+ tags$ul(
309
+ tags$li("Hanya angka yang bersebelahan langsung dengan kotak kosong yang dapat dipindahkan"),
310
+ tags$li("Anda bisa memindahkan angka ke atas, bawah, kiri, atau kanan ke kotak kosong"),
311
+ tags$li("Klik pada angka yang ingin Anda pindahkan (harus bersebelahan dengan kotak kosong)")
312
+ ),
313
+ br(),
314
+ h4("Petunjuk:"),
315
+ p("Klik kotak angka yang bersebelahan dengan kotak kosong untuk memindahkannya."),
316
+ p("Susun angka 1-8 secara berurutan dengan kotak kosong di pojok kanan bawah.")
317
+ )
318
+ )
319
  ),
320
+ br()
321
  )
322
 
323
+ # Server
324
  server <- function(input, output, session) {
325
+ # Inisialisasi state permainan
326
+ game_state <- reactiveValues(
327
+ puzzle = create_solvable_puzzle(),
328
+ initial_puzzle = NULL,
329
+ moves = 0,
330
+ game_won = FALSE,
331
+ hints_used = 0,
332
+ hint_position = NULL,
333
+ solution = NULL,
334
+ solution_step = 0
335
+ )
336
+
337
+ # Simpan puzzle awal saat aplikasi dimulai
338
+ observe({
339
+ if(is.null(game_state$initial_puzzle)) {
340
+ game_state$initial_puzzle <- game_state$puzzle
341
+ # Hitung ulang langkah optimal (seharusnya sama dengan yang disimpan)
342
+ game_state$optimal_steps <- solve_puzzle_bfs(game_state$initial_puzzle)
343
+ }
344
+ })
345
+
346
+ # Observer untuk menghitung solusi setiap kali puzzle berubah
347
+ observe({
348
+ # Hanya hitung solusi jika game belum selesai
349
+ if(!game_state$game_won) {
350
+ # Hitung solusi menggunakan A* algorithm
351
+ solution <- solve_puzzle(game_state$puzzle)
352
+ game_state$solution <- solution
353
+ game_state$solution_step <- 0 # Reset langkah solusi
354
+ }
355
+ })
356
+
357
+ # Tangani klik pada kotak puzzle (melalui observer global)
358
+ observe({
359
+ # Cek semua kemungkinan input tile
360
+ for(i in 1:9) {
361
+ local({
362
+ tile_id <- i
363
+ input_name <- paste0("tile_", tile_id)
364
+
365
+ observeEvent(input[[input_name]], {
366
+ if(!game_state$game_won) {
367
+ # Panggil move_tile dan dapatkan hasil dengan status pergerakan
368
+ result <- move_tile(game_state$puzzle, tile_id)
369
+
370
+ # Hanya update puzzle dan increment moves jika pergerakan valid
371
+ if(result$moved) {
372
+ game_state$puzzle <- result$puzzle
373
+ game_state$moves <- game_state$moves + 1
374
+ game_state$hint_position <- NULL # Reset hint saat pemain bergerak
375
+ game_state$solution_step <- game_state$solution_step + 1 # Increment langkah solusi
376
+
377
+ # Cek apakah puzzle sudah selesai
378
+ if(is_solved(game_state$puzzle)) {
379
+ game_state$game_won <- TRUE
380
+ }
381
+ }
382
+ }
383
+ })
384
+ })
385
+ }
386
+ })
387
+
388
+ # Tombol permainan baru
389
+ observeEvent(input$new_game, {
390
+ message("New Game created")
391
+ new_puzzle <- create_solvable_puzzle()
392
+ game_state$puzzle <- new_puzzle
393
+ game_state$initial_puzzle <- new_puzzle # Update initial puzzle
394
+ game_state$moves <- 0
395
+ game_state$game_won <- FALSE
396
+ game_state$hints_used <- 0
397
+ game_state$hint_position <- NULL
398
+ game_state$solution <- NULL
399
+ game_state$solution_step <- 0
400
+ # Hitung ulang langkah optimal (seharusnya sama dengan yang disimpan)
401
+ game_state$optimal_steps <- solve_puzzle_bfs(game_state$initial_puzzle)
402
+ })
403
+
404
+ # Tombol reset
405
+ observeEvent(input$reset_game, {
406
+ message("Game reset")
407
+ if(!is.null(game_state$initial_puzzle)) {
408
+ game_state$puzzle <- game_state$initial_puzzle
409
+ game_state$moves <- 0
410
+ game_state$game_won <- FALSE
411
+ game_state$hint_position <- NULL
412
+ game_state$solution_step <- 0
413
+ # Hitung ulang langkah optimal (seharusnya sama dengan yang disimpan)
414
+ game_state$optimal_steps <- solve_puzzle_bfs(game_state$initial_puzzle)
415
+ }
416
+ })
417
+
418
+ # Tombol hint
419
+ observeEvent(input$hint_button, {
420
+ if(!game_state$game_won & game_state$hints_used <= max_hint) {
421
+ # Dapatkan hint dari solusi yang disimpan
422
+ hint_pos <- get_hint_from_solution(game_state$puzzle, game_state$solution, game_state$solution_step)
423
+
424
+ if(!is.null(hint_pos)) {
425
+ game_state$hint_position <- hint_pos
426
+ game_state$hints_used <- game_state$hints_used + 1
427
  }
428
+ }
429
+ })
430
+
431
+ # Render setiap tile secara individual
432
+ for(i in 1:9) {
433
+ local({
434
+ tile_index <- i
435
+
436
+ output[[paste0("tile_", tile_index)]] <- renderUI({
437
+ value <- game_state$puzzle[tile_index]
438
+
439
+ # Tentukan style berdasarkan apakah ini adalah hint
440
+ is_hint <- !is.null(game_state$hint_position) && game_state$hint_position == tile_index
441
+ hint_style <- if(is_hint) "box-shadow: 0 0 10px 3px orange; border: 3px solid orange;" else ""
442
+ size <- 200
443
+
444
+ if(value == 0) {
445
+ # Kotak kosong
446
+ div(
447
+ style = paste0("width: ", size, "px; height: ", size, "px; border: 2px solid #333;
448
+ background-color: #f0f0f0; display: flex; align-items: center;
449
+ justify-content: center; font-size: 24px; font-weight: bold;", hint_style),
450
+ ""
451
+ )
452
+ } else {
453
+ # Kotak dengan angka
454
+ actionButton(
455
+ paste0("tile_", tile_index),
456
+ label = as.character(value),
457
+ style = paste0("width: ", size, "px; height: ", size, "px; border: 2px solid #333;
458
+ background-color: #4CAF50; color: white; font-size: 24px;
459
+ font-weight: bold;", hint_style)
460
+ )
461
+ }
462
+ })
463
+ })
464
+ }
465
+
466
+ # Status permainan
467
+ output$game_status <- renderText({
468
+ blank_pos <- get_blank_position(game_state$puzzle)
469
+ return(blank_pos)
470
+ })
471
+ # Jumlah langkah
472
+ output$move_count <- renderText({
473
+ if(!is.null(game_state$optimal_steps) && game_state$optimal_steps >= 0) {
474
+ optimal <- game_state$optimal_steps
475
+ } else {
476
+ optimal <- "Menghitung..."
477
+ }
478
+ paste0(game_state$moves, " / ", optimal)
479
+ })
480
+
481
+ output$final_moves_count <- renderUI({
482
+ if(game_state$game_won) {
483
+ div(
484
+ style = "text-align: center; font-size: 16px;",
485
+ paste("Selesai dalam", game_state$moves, "langkah!")
486
+ )
487
+ }
488
+ })
489
 
490
+ # Jumlah hint yang digunakan
491
+ output$hint_count <- renderText({
492
+ paste(game_state$hints_used, "/ ", max_hint)
493
+ })
494
+
495
+ # Langkah optimal
496
+ output$optimal_steps <- renderText({
497
+ if(!is.null(game_state$optimal_steps) && game_state$optimal_steps >= 0) {
498
+ game_state$optimal_steps
499
+ } else {
500
+ "Menghitung..."
501
+ }
502
+ })
503
+
504
+ # Pesan hint
505
+ output$hint_message <- renderUI({
506
+ if(!is.null(game_state$hint_position) & game_state$hints_used < max_hint & !game_state$game_won) {
507
+ div(
508
+ style = "text-align: center; color: orange; font-size: 16px; font-weight: bold;",
509
+ icon("lightbulb"),
510
+ paste("Hint: Klik angka", game_state$puzzle[game_state$hint_position])
511
+ )
512
+ } else if(game_state$hints_used >= max_hint & !game_state$game_won) {
513
+ div(
514
+ style = "text-align: center; color: red; font-size: 16px; font-weight: bold;",
515
+ paste0("Anda telah menggunakan semua hint (", game_state$hints_used, " / ", max_hint, ")")
516
+ )
517
+ }
518
+ })
519
+
520
+ # Status kemenangan
521
+ output$game_won <- reactive({
522
+ return(game_state$game_won)
523
+ })
524
+
525
+ # Untuk conditionalPanel
526
+ output$game_state <- renderUI({
527
+ req(game_state$game_won)
528
+ if(game_state$game_won){
529
+ div(
530
+ style = "text-align: center; color: green; font-size: 20px; font-weight: bold;",
531
+ icon("trophy"),
532
+ "Selamat! Anda Menyelesaikan Puzzle!"
533
+ )
534
+ }
535
+ })
536
  }
537
 
538
+ # Jalankan aplikasi
539
+ shinyApp(ui = ui, server = server)