import os
import random
import json
import copy
from datetime import datetime
from pathlib import Path
# ============================================================
# CONFIGURATION
# ============================================================
SAVE_FILE = "2048_save.json"
HIGH_SCORE_FILE = "2048_scores.json"
GRID_SIZE = 4
# ============================================================
# COLORS (ANSI — works on Linux/Mac and Windows 10+)
# ============================================================
TILE_COLORS = {
0: "\033[90m", # dark gray
2: "\033[97m", # white
4: "\033[93m", # yellow
8: "\033[33m", # dark yellow
16: "\033[91m", # light red
32: "\033[31m", # red
64: "\033[95m", # magenta
128: "\033[94m", # light blue
256: "\033[34m", # blue
512: "\033[96m", # cyan
1024: "\033[36m", # dark cyan
2048: "\033[92m", # bright green
}
RESET = "\033[0m"
BOLD = "\033[1m"
def tile_color(val):
return TILE_COLORS.get(val, "\033[92m")
# ============================================================
# HIGH SCORE
# ============================================================
def load_high_scores():
if Path(HIGH_SCORE_FILE).exists():
try:
with open(HIGH_SCORE_FILE, "r") as f:
return json.load(f)
except:
pass
return []
def save_high_score(score, moves):
scores = load_high_scores()
scores.append({
"score": score,
"moves": moves,
"date": datetime.now().strftime("%d-%m-%Y %H:%M"),
})
scores = sorted(scores, key=lambda x: x["score"], reverse=True)[:10]
with open(HIGH_SCORE_FILE, "w") as f:
json.dump(scores, f, indent=2)
def show_high_scores():
scores = load_high_scores()
print("\n" + "="*45)
print(" TOP 10 HIGH SCORES")
print("="*45)
if not scores:
print(" No scores yet. Play a game!")
else:
print(f" {'#':<4} {'SCORE':>10} {'MOVES':>6} DATE")
print(" " + "-"*40)
for i, s in enumerate(scores, 1):
print(f" {i:<4} {s['score']:>10,} {s['moves']:>6} {s['date']}")
print("="*45)
# ============================================================
# SAVE / LOAD GAME
# ============================================================
def save_game(board, score, moves):
with open(SAVE_FILE, "w") as f:
json.dump({"board": board, "score": score, "moves": moves}, f)
print(" Game saved!")
def load_game():
if Path(SAVE_FILE).exists():
try:
with open(SAVE_FILE, "r") as f:
data = json.load(f)
return data["board"], data["score"], data["moves"]
except:
pass
return None, None, None
# ============================================================
# BOARD OPERATIONS
# ============================================================
def new_board():
board = [[0] * GRID_SIZE for _ in range(GRID_SIZE)]
board = add_tile(board)
board = add_tile(board)
return board
def add_tile(board):
empty = [(r, c) for r in range(GRID_SIZE)
for c in range(GRID_SIZE) if board[r][c] == 0]
if empty:
r, c = random.choice(empty)
board[r][c] = 4 if random.random() < 0.1 else 2
return board
def slide_row_left(row):
"""Slide and merge a single row to the left. Returns (new_row, score_gained)."""
tiles = [x for x in row if x != 0]
merged = []
score = 0
i = 0
while i < len(tiles):
if i + 1 < len(tiles) and tiles[i] == tiles[i + 1]:
val = tiles[i] * 2
merged.append(val)
score += val
i += 2
else:
merged.append(tiles[i])
i += 1
merged += [0] * (GRID_SIZE - len(merged))
return merged, score
def move_left(board):
new_board = []
score = 0
for row in board:
new_row, s = slide_row_left(row)
new_board.append(new_row)
score += s
return new_board, score
def move_right(board):
flipped = [row[::-1] for row in board]
moved, score = move_left(flipped)
return [row[::-1] for row in moved], score
def transpose(board):
return [list(row) for row in zip(*board)]
def move_up(board):
t = transpose(board)
moved, score = move_left(t)
return transpose(moved), score
def move_down(board):
t = transpose(board)
moved, score = move_right(t)
return transpose(moved), score
def board_changed(old, new):
return any(old[r][c] != new[r][c]
for r in range(GRID_SIZE)
for c in range(GRID_SIZE))
def is_game_over(board):
"""Check if no moves are possible."""
# Any empty cell?
for r in range(GRID_SIZE):
for c in range(GRID_SIZE):
if board[r][c] == 0:
return False
# Any adjacent equal cells?
for r in range(GRID_SIZE):
for c in range(GRID_SIZE):
val = board[r][c]
if c + 1 < GRID_SIZE and board[r][c + 1] == val:
return False
if r + 1 < GRID_SIZE and board[r + 1][c] == val:
return False
return True
def has_won(board):
return any(board[r][c] >= 2048
for r in range(GRID_SIZE)
for c in range(GRID_SIZE))
def max_tile(board):
return max(board[r][c]
for r in range(GRID_SIZE)
for c in range(GRID_SIZE))
# ============================================================
# DISPLAY BOARD
# ============================================================
def clear():
os.system("cls" if os.name == "nt" else "clear")
def display_board(board, score, best, moves):
clear()
cell_w = 7
print("\n" + "="*38)
print(f" {BOLD}2048{RESET} "
f"Score: {BOLD}{score:>7,}{RESET} Best: {best:>7,}")
print(f" Moves: {moves:<6} Max tile: {max_tile(board)}")
print("="*38)
# Top border
border = " +" + (("-" * cell_w + "+") * GRID_SIZE)
print(border)
for row in board:
# Tile values
line = " |"
for val in row:
color = tile_color(val)
if val == 0:
line += f"{' ':^{cell_w}}|"
else:
val_str = str(val)
padded = val_str.center(cell_w)
line += f"{color}{BOLD}{padded}{RESET}|"
print(line)
print(border)
print()
print(" W/↑=Up S/↓=Down A/←=Left D/→=Right")
print(" P=Save Q=Quit N=New Game H=High Scores")
# ============================================================
# UNDO SUPPORT
# ============================================================
class UndoStack:
def __init__(self, max_size=5):
self.stack = []
self.max_size = max_size
def push(self, board, score):
self.stack.append((copy.deepcopy(board), score))
if len(self.stack) > self.max_size:
self.stack.pop(0)
def pop(self):
if self.stack:
return self.stack.pop()
return None, None
def can_undo(self):
return len(self.stack) > 0
# ============================================================
# GAME LOOP
# ============================================================
def game_loop(board=None, score=0, moves=0, best=0):
if board is None:
board = new_board()
undo = UndoStack()
while True:
display_board(board, score, max(score, best), moves)
# Win check
if has_won(board):
print(f"\n YOU REACHED 2048! Congratulations!")
cont = input(" Keep playing? (y/n): ").strip().lower()
if cont != "y":
save_high_score(score, moves)
return score
# Game over check
if is_game_over(board):
print(f"\n GAME OVER! Final score: {score:,}")
save_high_score(score, moves)
return score
# Can undo hint
if undo.can_undo():
print(f" U=Undo ({len(undo.stack)} available)", end=" ")
print()
try:
key = input(" Move: ").strip().lower()
except (EOFError, KeyboardInterrupt):
print("\n Game interrupted.")
save_high_score(score, moves)
return score
if key in ("q", "quit"):
print(f"\n Final score: {score:,} Moves: {moves}")
save_high_score(score, moves)
return score
elif key == "n":
confirm = input(" Start new game? (y/n): ").strip().lower()
if confirm == "y":
save_high_score(score, moves)
return game_loop(best=max(score, best))
elif key == "p":
save_game(board, score, moves)
continue
elif key == "h":
show_high_scores()
input(" Press Enter to continue...")
continue
elif key == "u":
prev_board, prev_score = undo.pop()
if prev_board:
board = prev_board
score = prev_score
moves = max(0, moves - 1)
print(" Undone!")
else:
print(" Nothing to undo.")
continue
# Moves
move_map = {
"w": move_up, "up": move_up,
"s": move_down, "down": move_down,
"a": move_left, "left": move_left,
"d": move_right, "right": move_right,
}
move_fn = move_map.get(key)
if not move_fn:
continue
# Save state for undo
undo.push(board, score)
new_b, gained = move_fn(board)
if board_changed(board, new_b):
board = add_tile(new_b)
score += gained
moves += 1
else:
undo.pop() # no change — remove saved state
return score
# ============================================================
# MAIN MENU
# ============================================================
def main():
best = 0
scores = load_high_scores()
if scores:
best = scores[0]["score"]
while True:
clear()
print("\n" + "="*38)
print(f" {BOLD} 2048 — Terminal Edition{RESET}")
print("="*38)
print(f" Best score: {best:,}\n")
print(" 1. New Game")
print(" 2. Continue saved game")
print(" 3. High Scores")
print(" 4. How to play")
print(" 0. Exit")
print("="*38)
choice = input("\n > ").strip()
if choice == "1":
result = game_loop(best=best)
best = max(best, result)
elif choice == "2":
board, score, moves = load_game()
if board:
print(f"\n Loaded game! Score: {score:,} Moves: {moves}")
input(" Press Enter to continue...")
result = game_loop(board, score, moves, best)
best = max(best, result)
else:
print(" No saved game found.")
input(" Press Enter...")
elif choice == "3":
show_high_scores()
input("\n Press Enter to return...")
elif choice == "4":
clear()
print("\n" + "="*50)
print(" HOW TO PLAY 2048")
print("="*50)
print("""
Goal: Combine tiles to reach the 2048 tile!
Controls:
W / ↑ — Slide tiles UP
S / ↓ — Slide tiles DOWN
A / ← — Slide tiles LEFT
D / → — Slide tiles RIGHT
U — Undo last move (up to 5 times)
P — Save game
N — New game
H — High scores
Q — Quit
Rules:
• Tiles with the same number merge when they collide.
• Each merge doubles the tile value and adds to score.
• A new tile (2 or 4) appears after each move.
• Game ends when no moves are possible.
• Reach 2048 to WIN — but keep going for a higher score!
Scoring:
• Each merge = merged tile value added to score.
• e.g. merging two 64s = +128 points.
""")
input(" Press Enter to return...")
elif choice == "0":
print("\n Thanks for playing! Goodbye!\n")
break
else:
print(" Invalid choice.")
# ============================================================
# RUN
# ============================================================
if __name__ == "__main__":
main()
No comments:
Post a Comment