From 1736e2f4aba8ff5ad79c0bd20f219756e460a464 Mon Sep 17 00:00:00 2001 From: Evelyn Sucitro Date: Thu, 4 Dec 2025 22:46:04 +0700 Subject: [PATCH] Update --- 2048.css | 63 ---- 2048.html | 18 +- Audio_2048.js => 2048_Audio.js | 0 ...cles_2048.js => 2048_Floating_Particles.js | 0 Main_2048.js => 2048_Main.js | 0 2048_Tiles_Colors.css | 295 ++++++++++++++++++ Tutorial_Logic.js => 2048_Tutorial_Logic.js | 0 ...nterface_2048.js => 2048_User_Interface.js | 0 ..._Effects_2048.js => 2048_Visual_Effects.js | 0 Bot_2048.py | 192 ++++++++++++ Logout.js | 4 +- 11 files changed, 496 insertions(+), 76 deletions(-) rename Audio_2048.js => 2048_Audio.js (100%) rename Floating_Particles_2048.js => 2048_Floating_Particles.js (100%) rename Main_2048.js => 2048_Main.js (100%) create mode 100644 2048_Tiles_Colors.css rename Tutorial_Logic.js => 2048_Tutorial_Logic.js (100%) rename User_Interface_2048.js => 2048_User_Interface.js (100%) rename Visual_Effects_2048.js => 2048_Visual_Effects.js (100%) create mode 100644 Bot_2048.py diff --git a/2048.css b/2048.css index 96c835d..2698a67 100644 --- a/2048.css +++ b/2048.css @@ -533,69 +533,6 @@ h1 { 75% { transform: translateX(10px); } } -/* Tile Colors */ -.tile-2 { - background: #00eaff55; - box-shadow: 0 0 12px #00eaff; -} - -.tile-4 { - background: #00ff9955; - box-shadow: 0 0 12px #00ff99; -} - -.tile-8 { - background: #ff00ff55; - box-shadow: 0 0 12px #ff00ff; -} - -.tile-16 { - background: #ff006655; - box-shadow: 0 0 12px #ff0066; -} - -.tile-32 { - background: #ffaa0055; - box-shadow: 0 0 12px #ffaa00; -} - -.tile-64 { - background: #ff000055; - box-shadow: 0 0 12px #ff0000; -} - -.tile-128 { - background: #5f00ff55; - box-shadow: 0 0 12px #5f00ff; -} - -.tile-256 { - background: #00ffea55; - box-shadow: 0 0 12px #00ffea; -} - -.tile-512 { - background: #ff00aa55; - box-shadow: 0 0 12px #ff00aa; -} - -.tile-1024 { - background: #00ffaa55; - box-shadow: 0 0 12px #00ffaa; -} - -.tile-2048 { - background: #ffd70066; - box-shadow: 0 0 18px #ffd700; - animation: goldShimmer 2.6s infinite; -} - -@keyframes goldShimmer { - 0% { box-shadow: 0 0 12px #ffd70066; transform: translateZ(0); } - 50% { box-shadow: 0 0 30px #ffd700aa; transform: translateY(-2px); } - 100% { box-shadow: 0 0 12px #ffd70066; transform: translateZ(0); } -} - /* Enhanced Merge Animation */ .tile.merge { animation: mergeBounce 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); diff --git a/2048.html b/2048.html index fd1c92d..7c8161c 100644 --- a/2048.html +++ b/2048.html @@ -4,14 +4,10 @@ 2048 - - - - - + - - - - + + + + - - + + \ No newline at end of file diff --git a/Audio_2048.js b/2048_Audio.js similarity index 100% rename from Audio_2048.js rename to 2048_Audio.js diff --git a/Floating_Particles_2048.js b/2048_Floating_Particles.js similarity index 100% rename from Floating_Particles_2048.js rename to 2048_Floating_Particles.js diff --git a/Main_2048.js b/2048_Main.js similarity index 100% rename from Main_2048.js rename to 2048_Main.js diff --git a/2048_Tiles_Colors.css b/2048_Tiles_Colors.css new file mode 100644 index 0000000..2be7097 --- /dev/null +++ b/2048_Tiles_Colors.css @@ -0,0 +1,295 @@ +.tile-2 { + background: #00eaff55; + box-shadow: 0 0 12px #00eaff; +} + +.tile-4 { + background: #00ff9955; + box-shadow: 0 0 12px #00ff99; +} + +.tile-8 { + background: #ff00ff55; + box-shadow: 0 0 12px #ff00ff; +} + +.tile-16 { + background: #ff006655; + box-shadow: 0 0 12px #ff0066; +} + +.tile-32 { + background: #ffaa0055; + box-shadow: 0 0 12px #ffaa00; +} + +.tile-64 { + background: #ff000055; + box-shadow: 0 0 12px #ff0000; +} + +.tile-128 { + background: #5f00ff55; + box-shadow: 0 0 12px #5f00ff; +} + +.tile-256 { + background: rgba(75, 0, 130, 0.6); + box-shadow: 0 0 20px #4b0082; + color: white !important; +} + +.tile-512 { + background: rgba(255, 69, 0, 0.6); + box-shadow: 0 0 25px #ff4500; + color: white !important; +} + +.tile-1024 { + background: rgba(212, 115, 130, 0.65); + box-shadow: 0 0 25px #d47382; + color: white !important; + border: 1px solid rgba(255, 255, 255, 0.3); +} + +.tile-2048 { + background: #ffd70066; + box-shadow: 0 0 18px #ffd700; + animation: goldShimmer 2.6s infinite; +} + +@keyframes goldShimmer { + 0% { box-shadow: 0 0 12px #ffd70066; transform: translateZ(0); } + 50% { box-shadow: 0 0 30px #ffd700aa; transform: translateY(-2px); } + 100% { box-shadow: 0 0 12px #ffd70066; transform: translateZ(0); } +} + +.tile-4096 { + background: #ff220066; + box-shadow: 0 0 20px #ff2200; +} + +.tile-8192 { + background: #0044ff66; + box-shadow: 0 0 20px #0044ff; +} + +.tile-16384 { + background: #ccff0066; + box-shadow: 0 0 20px #ccff00; +} + +.tile-32768 { + background: #7700ff66; + box-shadow: 0 0 25px #7700ff; +} + +.tile-65536 { + background: #000000aa; + box-shadow: 0 0 30px #ffffff, inset 0 0 10px #ffffff; + border: 1px solid rgba(255,255,255,0.3); + animation: pulseWhite 2s infinite; +} + +@keyframes pulseWhite { + 0% { box-shadow: 0 0 20px #ffffff; } + 50% { box-shadow: 0 0 40px #ffffff, 0 0 10px #00eaff; } + 100% { box-shadow: 0 0 20px #ffffff; } +} + +.tile-131072 { + background: linear-gradient(135deg, + rgba(255, 0, 0, 0.5), + rgba(0, 255, 0, 0.5), + rgba(0, 0, 255, 0.5) + ); + background-size: 200% 200%; + color: white !important; + text-shadow: 0 1px 3px rgba(0,0,0,0.8); + box-shadow: 0 0 30px rgba(255, 255, 255, 0.5); + border: 1px solid rgba(255, 255, 255, 0.5); + animation: holoMove 3s ease infinite; +} + +@keyframes holoMove { + 0% { + background-position: 0% 50%; + box-shadow: 0 0 25px #ff00ff; + } + 50% { + background-position: 100% 50%; + box-shadow: 0 0 25px #00ffff; + } + 100% { + background-position: 0% 50%; + box-shadow: 0 0 25px #ff00ff; + } +} + +.tile-262144 { + background: linear-gradient(-45deg, #ff0080, #ff8c00, #40e0d0, #9932cc); + background-size: 300% 300%; + color: white !important; + text-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); + box-shadow: + 0 0 30px rgba(255, 255, 255, 0.6), + inset 0 0 20px rgba(255, 255, 255, 0.4); + border: 1px solid rgba(255, 255, 255, 0.8); + animation: auroraFlow 5s ease infinite alternate; +} + +@keyframes auroraFlow { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } +} + +.tile-524288 { + background: radial-gradient(circle at center, #ffffac 0%, #ffeb3b 30%, #ff9800 60%, #d50000 100%); + background-size: 200% 200%; + color: white !important; + text-shadow: 0 2px 4px rgba(0,0,0,0.8); + box-shadow: + 0 0 30px #ff5722, + 0 0 60px #ffc107, + inset 0 0 20px rgba(255, 255, 255, 0.5); + border: 2px solid rgba(255, 255, 255, 0.6); + animation: sunBurn 3s infinite alternate ease-in-out; +} + +@keyframes sunBurn { + 0% { + background-position: 50% 50%; + box-shadow: 0 0 30px #ff5722, 0 0 50px #ff9800; + transform: scale(1); + } + 100% { + background-position: 60% 40%; + box-shadow: 0 0 50px #ff5722, 0 0 80px #ffeb3b; + transform: scale(1.02); + } +} + +.tile-1048576 { + background: radial-gradient(circle, #ffffff 10%, #00ffff 30%, #9400d3 70%, #000000 100%); + background-size: 300% 300%; + color: white !important; + box-shadow: + 0 0 40px #00ffff, + 0 0 80px #9400d3, + inset 0 0 30px #ffffff; + border: 2px solid #ffffff; + animation: supernovaPulse 0.5s infinite alternate ease-in-out; +} + +@keyframes supernovaPulse { + 0% { + box-shadow: 0 0 40px #00ffff, 0 0 80px #9400d3; + transform: scale(1); + } + 100% { + box-shadow: 0 0 60px #00ffff, 0 0 100px #ff00ff; + transform: scale(1.03); + } +} + +.tile-2097152 { + background: linear-gradient( + 45deg, + #240b36 0%, + #c31432 25%, + #240b36 50%, + #00d2ff 75%, + #240b36 100% + ); + background-size: 400% 400%; + color: #ffffff !important; + text-shadow: 0 0 5px #00d2ff, 0 0 10px #c31432; + box-shadow: + 0 0 20px rgba(138, 43, 226, 0.8), + inset 0 0 15px rgba(0, 210, 255, 0.3); + border: 1px solid #00d2ff; + animation: galaxyFlow 6s ease infinite; +} + +@keyframes galaxyFlow { + 0% { + background-position: 0% 50%; + border-color: #00d2ff; + } + 50% { + background-position: 100% 50%; + border-color: #c31432; + } + 100% { + background-position: 0% 50%; + border-color: #00d2ff; + } +} + +.tile-4194304 { + background-color: #330000; + background-image: + radial-gradient(circle at 50% 50%, transparent 20%, #000000 120%), + linear-gradient(45deg, #ff4500 25%, #ff8c00 50%, #ff4500 75%); + color: #fffacd !important; + text-shadow: 0 0 5px #ff0000; + box-shadow: + 0 0 20px #ff4500, + inset 0 0 10px #8b0000; + border: 2px solid #ff8c00; + font-size: clamp(22px, 1.6vmin, 14px); + animation: magmaPulse 0.8s infinite alternate; +} + +@keyframes magmaPulse { + 0% { + box-shadow: 0 0 15px #ff4500; + border-color: #ff4500; + } + 100% { + box-shadow: 0 0 35px #ff8c00; + border-color: #ffff00; + } +} + +.tile-8388608 { + background: linear-gradient( + 125deg, + #000000 0%, + #3a0000 40%, + #800000 50%, + #3a0000 60%, + #000000 100% + ); + background-size: 300% 300%; + color: white !important; + text-shadow: 0 0 10px #ff0000; + box-shadow: + 0 0 30px #800000, + inset 0 0 20px rgba(255, 0, 0, 0.2); + border: 1px solid #ff0000; + font-size: clamp(21px, 1.6vmin, 14px); + animation: eclipseMove 4s ease infinite; +} + +@keyframes eclipseMove { + 0% { + background-position: 0% 50%; + box-shadow: 0 0 20px #800000; + } + 50% { + background-position: 100% 50%; + box-shadow: 0 0 50px #ff0000; + } + 100% { + background-position: 0% 50%; + box-shadow: 0 0 20px #800000; + } +} \ No newline at end of file diff --git a/Tutorial_Logic.js b/2048_Tutorial_Logic.js similarity index 100% rename from Tutorial_Logic.js rename to 2048_Tutorial_Logic.js diff --git a/User_Interface_2048.js b/2048_User_Interface.js similarity index 100% rename from User_Interface_2048.js rename to 2048_User_Interface.js diff --git a/Visual_Effects_2048.js b/2048_Visual_Effects.js similarity index 100% rename from Visual_Effects_2048.js rename to 2048_Visual_Effects.js diff --git a/Bot_2048.py b/Bot_2048.py new file mode 100644 index 0000000..546578c --- /dev/null +++ b/Bot_2048.py @@ -0,0 +1,192 @@ +import time +import random +import numpy as np +from selenium import webdriver +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.chrome.service import Service +from selenium.webdriver.chrome.options import Options +from webdriver_manager.chrome import ChromeDriverManager + +URL_LOGIN = "http://localhost/Kelompok06_2048/Homepage.html" + +SEARCH_DEPTH = 4 + +DELAY_ANIMATION = 0.1 + +class GameAI: + def __init__(self, url): + print(">>> Menyiapkan Browser Mode HARDCORE (Depth 4)...") + chrome_options = Options() + chrome_options.add_argument("--log-level=3") + + chrome_options.add_experimental_option("detach", True) + + self.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options) + self.driver.set_window_size(1000, 1000) + + self.driver.get(url) + print("\n" + "="*50) + print(" SILAKAN LOGIN MANUAL -> KLIK PLAY ") + print(" Bot Depth 4 siap menunggu di arena...") + print("="*50 + "\n") + + try: + WebDriverWait(self.driver, 3600).until( + EC.presence_of_element_located((By.ID, "board")) + ) + print(">>> GAME START! Mengaktifkan Otak Jenius...") + time.sleep(1) + except: + print("Gagal deteksi game, tapi browser tetap terbuka.") + + self.body = self.driver.find_element(By.TAG_NAME, 'body') + + def get_grid(self): + matrix = np.zeros((4, 4), dtype=int) + try: + for r in range(4): + for c in range(4): + element_id = f"{r}-{c}" + try: + tile = self.driver.find_element(By.ID, element_id) + text = tile.text.strip() + if text.isdigit(): + matrix[r][c] = int(text) + else: + matrix[r][c] = 0 + except: + matrix[r][c] = 0 + except: + pass + return matrix + + def heuristic(self, grid): + weights = np.array([ + [0, 1, 10, 30], + [150, 100, 80, 50], + [200, 300, 500, 800], + [20000, 8000, 3000, 1500] + ]) + score = np.sum(grid * weights) + empty_count = len(np.where(grid == 0)[0]) + score += empty_count * 30000 + penalty = 0 + for x in range(4): + # Baris + current = 0 + next_val = 0 + for y in range(3): + current = grid[x][y] + next_val = grid[x][y+1] + if current > next_val: + score += current - next_val + else: + score -= (next_val - current) * 5 + + for y in range(3): + current = grid[y][x] + next_val = grid[y+1][x] + if current > next_val: + score += current - next_val + else: + score -= (next_val - current) * 5 + + return score + + def simulate_move(self, grid, direction): + temp_grid = grid.copy() + if direction == 'UP': temp_grid = np.rot90(temp_grid, 1) + elif direction == 'RIGHT': temp_grid = np.rot90(temp_grid, 2) + elif direction == 'DOWN': temp_grid = np.rot90(temp_grid, 3) + + changed = False + new_grid_vals = [] + for row in temp_grid: + new_row = [x for x in row if x != 0] + for i in range(len(new_row)-1): + if new_row[i] == new_row[i+1] and new_row[i] != 0: + new_row[i] *= 2 + new_row[i+1] = 0 + new_row = [x for x in new_row if x != 0] + new_row += [0] * (4 - len(new_row)) + new_grid_vals.append(new_row) + if new_row != list(row): changed = True + + temp_grid = np.array(new_grid_vals) + if direction == 'UP': temp_grid = np.rot90(temp_grid, 3) + elif direction == 'RIGHT': temp_grid = np.rot90(temp_grid, 2) + elif direction == 'DOWN': temp_grid = np.rot90(temp_grid, 1) + return temp_grid, changed + + def expectimax(self, grid, depth, is_player_turn): + if depth == 0: return self.heuristic(grid) + + if is_player_turn: + best_score = -float('inf') + for move in ['DOWN', 'RIGHT', 'LEFT', 'UP']: + new_grid, changed = self.simulate_move(grid, move) + if changed: + score = self.expectimax(new_grid, depth - 1, False) + best_score = max(best_score, score) + return best_score if best_score != -float('inf') else -10000000 + else: + empty_cells = list(zip(*np.where(grid == 0))) + if not empty_cells: return self.heuristic(grid) + + sample_size = 4 + if len(empty_cells) > sample_size: + empty_cells = random.sample(empty_cells, sample_size) + + total_score = 0 + for r, c in empty_cells: + grid[r][c] = 2 + total_score += self.expectimax(grid, depth - 1, True) + grid[r][c] = 0 + return total_score / len(empty_cells) + + def run(self): + print(">>> Bot Mulai! (Mode: FULL DEPTH 4)") + print(">>> Tips: Jangan minimize window browser biar lancar.") + + while True: + try: + grid = self.get_grid() + + if np.sum(grid) == 0: + time.sleep(0.5) + continue + + best_move = None + max_val = -float('inf') + valid_found = False + + empty = len(np.where(grid == 0)[0]) + print(f"\rMikir... (Kosong: {empty})", end="") + + for move in ['DOWN', 'RIGHT', 'LEFT', 'UP']: + sim_grid, changed = self.simulate_move(grid, move) + if changed: + valid_found = True + val = self.expectimax(sim_grid, SEARCH_DEPTH, False) + if val > max_val: max_val = val; best_move = move + + if best_move: + self.body.send_keys(getattr(Keys, best_move)) + time.sleep(DELAY_ANIMATION) + else: + if not valid_found: + print("\nGAME OVER!") + break + else: + self.body.send_keys(Keys.DOWN) + except KeyboardInterrupt: + break + except Exception: + time.sleep(1) + +if __name__ == "__main__": + ai = GameAI(URL_LOGIN) + ai.run() \ No newline at end of file diff --git a/Logout.js b/Logout.js index 2ca07aa..363d409 100644 --- a/Logout.js +++ b/Logout.js @@ -97,8 +97,8 @@ // Redirect after 1.5 seconds setTimeout(() => { - window.location.href = 'index.html'; - }, 1500); + window.location.href = 'Homepage.html'; + }, 2000); } // ==================== SHOW SUCCESS MODAL ====================