diff --git a/__pycache__/mobile-stone-paper-scissors.cpython-313.pyc b/__pycache__/mobile-stone-paper-scissors.cpython-313.pyc new file mode 100644 index 0000000..22d0ede Binary files /dev/null and b/__pycache__/mobile-stone-paper-scissors.cpython-313.pyc differ diff --git a/assets/paper.png b/assets/paper.png new file mode 100644 index 0000000..29d3d34 Binary files /dev/null and b/assets/paper.png differ diff --git a/assets/rock.png b/assets/rock.png new file mode 100644 index 0000000..1c8ca9e Binary files /dev/null and b/assets/rock.png differ diff --git a/assets/scissors.png b/assets/scissors.png new file mode 100644 index 0000000..b154870 Binary files /dev/null and b/assets/scissors.png differ diff --git a/flappy_bird/flappy_bird.py b/flappy_bird/flappy_bird.py new file mode 100644 index 0000000..350d065 --- /dev/null +++ b/flappy_bird/flappy_bird.py @@ -0,0 +1,381 @@ +""" +Flappy Bird - Polished Tkinter Version +Controls: + - Space / Up arrow / Left mouse click -> flap / start / restart + - P -> pause / resume + - R -> restart after game over +""" +import tkinter as tk +import random +import time +import json +import os + +# ----------------- Config ----------------- +WIDTH = 420 +HEIGHT = 640 +FPS_MS = 16 # ~60 FPS +BIRD_SIZE = 34 +BIRD_X = 110 # fixed horizontal position for bird +GRAVITY = 900.0 # pixels / s^2 +FLAP_V = -340.0 # instant velocity on flap (px / s) +PIPE_WIDTH = 72 +PIPE_GAP = 170 +PIPE_SPEED = 180.0 # pixels / second +SPAWN_INTERVAL = 1.6 # seconds between pipes +SAVE_FILE = "flappy_highscore.json" +BG_COLOR = "#70c5ce" +GROUND_HEIGHT = 80 +TEXT_FONT = ("Arial", 18, "bold") +SCORE_FONT = ("Arial", 28, "bold") + +# ----------------- Helpers ----------------- +def load_highscore(): + if os.path.exists(SAVE_FILE): + try: + with open(SAVE_FILE, "r") as f: + data = json.load(f) + return int(data.get("high_score", 0)) + except Exception: + return 0 + return 0 + +def save_highscore(score): + try: + with open(SAVE_FILE, "w") as f: + json.dump({"high_score": int(score)}, f) + except Exception: + pass + +def rects_intersect(a, b): + # a and b are (x1,y1,x2,y2) + return not (a[2] <= b[0] or a[0] >= b[2] or a[3] <= b[1] or a[1] >= b[3]) + +# ----------------- Game Class ----------------- +class FlappyBird: + def __init__(self, root): + self.root = root + self.root.title("Flappy Bird — Polished Tkinter") + self.canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT, bg=BG_COLOR, highlightthickness=0) + self.canvas.pack() + + # state + self.state = "menu" # menu | playing | paused | gameover + self.last_time = None + self._job = None + + # game objects + self.bird_id = None + self.bird_v = 0.0 + self.pipes = [] # list of dicts {x, gap_y, top_id, bottom_id, scored} + self.spawn_timer = 0.0 + self.score = 0 + self.high_score = load_highscore() + + # ui + self.score_text_id = None + self.overlay_ids = [] + self.pause_text_id = None + + # input bindings + root.bind("", self.on_flap) + root.bind("", self.on_flap) + root.bind("", self.on_flap) # mouse left click + root.bind("p", self.toggle_pause) + root.bind("P", self.toggle_pause) + root.bind("r", self.force_restart) + root.protocol("WM_DELETE_WINDOW", self._on_close) + + # draw static ground + self.draw_background() + self.show_menu() + + # ---------- drawing ---------- + def draw_background(self): + self.canvas.delete("background") + # sky already BG_COLOR; draw ground strip + self.canvas.create_rectangle(0, HEIGHT - GROUND_HEIGHT, WIDTH, HEIGHT, fill="#de9b5c", width=0, tags="background") + # ground detail + self.canvas.create_rectangle(0, HEIGHT - GROUND_HEIGHT - 6, WIDTH, HEIGHT - GROUND_HEIGHT, fill="#c17e45", width=0, tags="background") + + def create_bird(self): + cy = HEIGHT//2 + x1 = BIRD_X - BIRD_SIZE//2 + y1 = cy - BIRD_SIZE//2 + x2 = BIRD_X + BIRD_SIZE//2 + y2 = cy + BIRD_SIZE//2 + self.bird_id = self.canvas.create_oval(x1, y1, x2, y2, fill="#ffeb3b", outline="#d8b41b", width=2) + # simple eye + self.canvas.create_oval(BIRD_X+6, cy-6, BIRD_X+11, cy-1, fill="#222", tags=("bird_eye",)) + # wing (animated by moving it slightly) + self.wing_id = self.canvas.create_polygon(BIRD_X-4, cy, BIRD_X+2, cy-4, BIRD_X+6, cy, fill="#f0c419", outline="") + + def clear_game_objects(self): + if self.bird_id: + self.canvas.delete(self.bird_id) + self.canvas.delete("pipe") + self.canvas.delete("score") + self.canvas.delete("overlay") + self.canvas.delete("bird_eye") + try: + del self.wing_id + except Exception: + pass + self.pipes.clear() + + # ---------- state screens ---------- + def show_menu(self): + self.canvas.delete("all") + self.draw_background() + self.overlay_ids = [] + title = self.canvas.create_text(WIDTH//2, HEIGHT//2 - 70, text="Flappy Bird", font=("Helvetica", 40, "bold"), fill="white", tags="overlay") + hint = self.canvas.create_text(WIDTH//2, HEIGHT//2 - 20, text="Press SPACE / Click to Start", font=TEXT_FONT, fill="#ffc107", tags="overlay") + sub = self.canvas.create_text(WIDTH//2, HEIGHT//2 + 20, text="Space/Up/Click = Flap P = Pause R = Restart", font=("Helvetica", 12), fill="white", tags="overlay") + hs = self.canvas.create_text(WIDTH//2, HEIGHT//2 + 70, text=f"High Score: {self.high_score}", font=("Helvetica", 16), fill="white", tags="overlay") + self.overlay_ids = [title, hint, sub, hs] + self.state = "menu" + # set up a light demo bird to bob up/down + self.demo_bob = 0.0 + self._demo_bob_job() + + def _demo_bob_job(self): + # small bob animation while on menu + if self.state != "menu": + return + self.canvas.delete("demo_bird") + bob_y = HEIGHT//2 + int(8 * (1 + random.random()) * (0.5 - 0.5)) + # draw a simple bird sample + bx1 = BIRD_X - 20 + by1 = HEIGHT//2 - 20 + bx2 = BIRD_X + 20 + by2 = HEIGHT//2 + 20 + self.canvas.create_oval(bx1, by1, bx2, by2, fill="#ffeb3b", outline="#d8b41b", width=2, tags="demo_bird") + # loop slow + self.root.after(700, self._demo_bob_job) + + def show_gameover(self): + self.state = "gameover" + # overlay + self.overlay_ids = [] + panel = self.canvas.create_rectangle(WIDTH//2 - 180, HEIGHT//2 - 90, WIDTH//2 + 180, HEIGHT//2 + 90, fill="#000000cc", outline="", tags="overlay") + t1 = self.canvas.create_text(WIDTH//2, HEIGHT//2 - 30, text="GAME OVER", font=("Helvetica", 32, "bold"), fill="#ff5252", tags="overlay") + t2 = self.canvas.create_text(WIDTH//2, HEIGHT//2 + 10, text=f"Score: {self.score} High: {self.high_score}", font=TEXT_FONT, fill="white", tags="overlay") + t3 = self.canvas.create_text(WIDTH//2, HEIGHT//2 + 50, text="Press SPACE / Click / R to Replay", font=("Helvetica", 14), fill="#ffc107", tags="overlay") + self.overlay_ids = [panel, t1, t2, t3] + + # ---------- input handlers ---------- + def on_flap(self, event=None): + # universal handler: acts depending on state + if self.state == "menu": + # start playing + self.start_game() + return + if self.state == "playing": + # flap + self.bird_v = FLAP_V + # small wing animation + try: + self.canvas.coords(self.wing_id, BIRD_X-4, (self.canvas.coords(self.bird_id)[1]+self.canvas.coords(self.bird_id)[3])/2, + BIRD_X+2, self.canvas.coords(self.bird_id)[1]+6, BIRD_X+6, (self.canvas.coords(self.bird_id)[1]+self.canvas.coords(self.bird_id)[3])/2) + except Exception: + pass + return + if self.state == "gameover": + # restart on flap/click + self.restart() + return + if self.state == "paused": + # unpause + flap + self.toggle_pause() + self.bird_v = FLAP_V + return + + def toggle_pause(self, event=None): + if self.state == "playing": + self.state = "paused" + if self.pause_text_id is None: + self.pause_text_id = self.canvas.create_text(WIDTH//2, HEIGHT//2, text="PAUSED", font=("Helvetica", 36, "bold"), fill="white", tags="overlay") + elif self.state == "paused": + self.canvas.delete(self.pause_text_id) + self.pause_text_id = None + self.last_time = time.time() + self.state = "playing" + self._schedule_next_frame() + + def force_restart(self, event=None): + # user pressed R + if self.state in ("playing", "paused", "gameover"): + self.restart() + + # ---------- game lifecycle ---------- + def start_game(self): + # clear overlays + demo + self.canvas.delete("demo_bird") + for item in self.overlay_ids: + try: self.canvas.delete(item) + except: pass + self.overlay_ids.clear() + + # reset + self.clear_game_objects() + self.create_bird() + self.score = 0 + self.pipes = [] + self.spawn_timer = 0.4 # spawn first pipe quickly + self.last_time = time.time() + self.bird_v = 0.0 + self.state = "playing" + + # score label + if self.score_text_id: + self.canvas.delete(self.score_text_id) + self.score_text_id = self.canvas.create_text(WIDTH - 16, 12, anchor="ne", text=f"{self.score}", font=SCORE_FONT, fill="white", tags="score") + + # start main loop + self._schedule_next_frame() + + def restart(self): + # cleanup any scheduled callback to avoid dupes + if self._job: + try: + self.root.after_cancel(self._job) + except Exception: + pass + self._job = None + # save high score + if self.score > self.high_score: + self.high_score = self.score + save_highscore(self.high_score) + # tiny flash or sound could go here + self.clear_game_objects() + self.start_game() + + def end_game(self): + self.state = "gameover" + # save high score + if self.score > self.high_score: + self.high_score = self.score + save_highscore(self.high_score) + self.show_gameover() + # stop loop (no scheduling next) + if self._job: + try: + self.root.after_cancel(self._job) + except Exception: + pass + self._job = None + + def _on_close(self): + # cancel scheduled job before closing + if self._job: + try: + self.root.after_cancel(self._job) + except Exception: + pass + self.root.destroy() + + # ---------- pipes ---------- + def spawn_pipe(self): + gap_y = random.randint(110, HEIGHT - GROUND_HEIGHT - 110 - PIPE_GAP) + x = WIDTH + 4 + top_id = self.canvas.create_rectangle(x, 0, x + PIPE_WIDTH, gap_y, fill="#2e8b57", width=0, tags="pipe") + bottom_id = self.canvas.create_rectangle(x, gap_y + PIPE_GAP, x + PIPE_WIDTH, HEIGHT - GROUND_HEIGHT, fill="#2e8b57", width=0, tags="pipe") + pipe = {"x": float(x), "gap_y": gap_y, "top": top_id, "bottom": bottom_id, "scored": False} + self.pipes.append(pipe) + + # ---------- main loop ---------- + def _schedule_next_frame(self): + # schedule next frame if playing + if self.state == "playing": + self._job = self.root.after(FPS_MS, self._frame) + + def _frame(self): + now = time.time() + dt = now - (self.last_time or now) + # clamp dt to avoid huge jumps + if dt > 0.05: + dt = 0.05 + self.last_time = now + + # update physics + self.bird_v += GRAVITY * dt + # convert bird velocity to movement in pixels this frame + dy = self.bird_v * dt + self.canvas.move(self.bird_id, 0, dy) + # move wing to follow + try: + bx1, by1, bx2, by2 = self.canvas.coords(self.bird_id) + mid_y = (by1 + by2)/2 + # basic wing follow + self.canvas.coords(self.wing_id, BIRD_X-4, mid_y, BIRD_X+2, mid_y-8, BIRD_X+6, mid_y) + self.canvas.coords("bird_eye", BIRD_X+6, mid_y-6, BIRD_X+11, mid_y-1) + except Exception: + pass + + # bird bounds check top/bottom (collide with ground) + bx1, by1, bx2, by2 = self.canvas.coords(self.bird_id) + if by1 <= 4: + # hit ceiling -> clamp and bounce a little + self.canvas.coords(self.bird_id, bx1, 4, bx2, 4 + (by2-by1)) + self.bird_v = 0.0 + by1 = 4 + if by2 >= HEIGHT - GROUND_HEIGHT: + # hit ground -> game over + self.end_game() + return + + # update pipes + remove_list = [] + for p in list(self.pipes): + p["x"] -= PIPE_SPEED * dt + x = p["x"] + # update rectangle coords + self.canvas.coords(p["top"], x, 0, x + PIPE_WIDTH, p["gap_y"]) + self.canvas.coords(p["bottom"], x, p["gap_y"] + PIPE_GAP, x + PIPE_WIDTH, HEIGHT - GROUND_HEIGHT) + + # scoring: when pipe right edge crosses bird_x and not yet scored + if (not p["scored"]) and (x + PIPE_WIDTH < BIRD_X): + p["scored"] = True + self.score += 1 + self.canvas.itemconfigure(self.score_text_id, text=str(self.score)) + + # remove off-screen pipes + if x + PIPE_WIDTH < -10: + remove_list.append(p) + + for p in remove_list: + try: + self.canvas.delete(p["top"]) + self.canvas.delete(p["bottom"]) + except Exception: + pass + if p in self.pipes: + self.pipes.remove(p) + + # spawn logic + self.spawn_timer -= dt + if self.spawn_timer <= 0.0: + self.spawn_pipe() + self.spawn_timer = SPAWN_INTERVAL + + # collision detection with pipes + bird_bbox = self.canvas.coords(self.bird_id) + for p in self.pipes: + top_bbox = self.canvas.coords(p["top"]) + bot_bbox = self.canvas.coords(p["bottom"]) + if rects_intersect(bird_bbox, top_bbox) or rects_intersect(bird_bbox, bot_bbox): + self.end_game() + return + + # schedule next + self._schedule_next_frame() + + # ---------- run ---------- + def run(self): + self.root.mainloop() + +# ----------------- run the game ----------------- +if __name__ == "__main__": + root = tk.Tk() + game = FlappyBird(root) + game.run() diff --git a/flappy_bird/flappy_bird_mobile.py b/flappy_bird/flappy_bird_mobile.py new file mode 100644 index 0000000..d1bf379 --- /dev/null +++ b/flappy_bird/flappy_bird_mobile.py @@ -0,0 +1,234 @@ +# flappy_bird_kivy_fixed.py +import kivy +kivy.require("2.3.0") + +from kivy.app import App +from kivy.uix.widget import Widget +from kivy.uix.label import Label +from kivy.graphics import Color, Rectangle, Ellipse +from kivy.clock import Clock +from kivy.core.window import Window +from kivy.utils import get_color_from_hex +import random, json, os, time + +WIDTH, HEIGHT = 420, 640 +FPS = 1 / 60 +BIRD_SIZE = 34 +BIRD_X = 110 +GRAVITY = -900.0 # Negative because up is positive in Kivy +FLAP_V = 340.0 +PIPE_WIDTH = 72 +PIPE_GAP = 170 +PIPE_SPEED = 180.0 +SPAWN_INTERVAL = 1.6 +SAVE_FILE = "flappy_highscore.json" +BG_COLOR = "#70c5ce" +GROUND_HEIGHT = 80 + +def load_highscore(): + if os.path.exists(SAVE_FILE): + try: + with open(SAVE_FILE, "r") as f: + return int(json.load(f).get("high_score", 0)) + except: + return 0 + return 0 + +def save_highscore(score): + try: + with open(SAVE_FILE, "w") as f: + json.dump({"high_score": int(score)}, f) + except: + pass + +def rects_intersect(a, b): + return not (a[2] <= b[0] or a[0] >= b[2] or a[3] <= b[1] or a[1] >= b[3]) + +class FlappyGame(Widget): + def __init__(self, **kwargs): + super().__init__(**kwargs) + Window.size = (WIDTH, HEIGHT) + Window.clearcolor = get_color_from_hex(BG_COLOR) + + self.state = "menu" + self.bird_y = HEIGHT // 2 + self.bird_v = 0 + self.pipes = [] + self.spawn_timer = 0 + self.score = 0 + self.high_score = load_highscore() + self.last_time = time.time() + + self.score_label = Label(text="", font_size=28, color=(1, 1, 1, 1), + pos=(WIDTH - 60, HEIGHT - 40)) + self.add_widget(self.score_label) + self.overlay_label = Label(text="", font_size=22, color=(1, 1, 0, 1), + halign="center", valign="middle", + size=(WIDTH, HEIGHT), pos=(0, 0)) + self.add_widget(self.overlay_label) + + self.show_menu() + + Window.bind(on_key_down=self._on_key_down) + Window.bind(on_mouse_down=self._on_mouse_down) + + def show_menu(self): + self.state = "menu" + self.overlay_label.text = ( + f"[b]Flappy Bird[/b]\n\nPress SPACE / Click to Start\n" + f"P = Pause R = Restart\nHigh Score: {self.high_score}" + ) + self.overlay_label.markup = True + + def start_game(self): + self.state = "playing" + self.bird_y = HEIGHT // 2 + self.bird_v = 0 + self.pipes.clear() + self.score = 0 + self.spawn_timer = 0.4 + self.overlay_label.text = "" + self.last_time = time.time() + Clock.schedule_interval(self.update, FPS) + + def spawn_pipe(self): + gap_y = random.randint(110, HEIGHT - GROUND_HEIGHT - 110 - PIPE_GAP) + self.pipes.append({"x": WIDTH, "gap_y": gap_y, "scored": False}) + + def update(self, _): + if self.state != "playing": + return + + now = time.time() + dt = now - self.last_time + if dt > 0.05: + dt = 0.05 + self.last_time = now + + # Bird physics + self.bird_v += GRAVITY * dt + self.bird_y += self.bird_v * dt + + # Ground / ceiling + if self.bird_y <= GROUND_HEIGHT: + self.bird_y = GROUND_HEIGHT + self.end_game() + if self.bird_y + BIRD_SIZE >= HEIGHT: + self.bird_y = HEIGHT - BIRD_SIZE + self.bird_v = 0 + + # Pipes + for p in self.pipes: + p["x"] -= PIPE_SPEED * dt + self.pipes = [p for p in self.pipes if p["x"] + PIPE_WIDTH > 0] + + # Spawn + self.spawn_timer -= dt + if self.spawn_timer <= 0: + self.spawn_pipe() + self.spawn_timer = SPAWN_INTERVAL + + # Collision & scoring + bird_rect = (BIRD_X, self.bird_y, + BIRD_X + BIRD_SIZE, self.bird_y + BIRD_SIZE) + for p in self.pipes: + if not p["scored"] and p["x"] + PIPE_WIDTH < BIRD_X: + p["scored"] = True + self.score += 1 + top_rect = (p["x"], p["gap_y"] + PIPE_GAP, + p["x"] + PIPE_WIDTH, HEIGHT) + bottom_rect = (p["x"], 0, + p["x"] + PIPE_WIDTH, p["gap_y"]) + if rects_intersect(bird_rect, top_rect) or rects_intersect(bird_rect, bottom_rect): + self.end_game() + + self.draw() + + def draw(self): + self.canvas.clear() + + # Background + with self.canvas: + Color(*get_color_from_hex(BG_COLOR)) + Rectangle(pos=(0, 0), size=(WIDTH, HEIGHT)) + + # Pipes + with self.canvas: + Color(*get_color_from_hex("#2e8b57")) + for p in self.pipes: + Rectangle(pos=(p["x"], 0), size=(PIPE_WIDTH, p["gap_y"])) + Rectangle(pos=(p["x"], p["gap_y"] + PIPE_GAP), + size=(PIPE_WIDTH, HEIGHT - (p["gap_y"] + PIPE_GAP))) + + # Ground + with self.canvas: + Color(*get_color_from_hex("#de9b5c")) + Rectangle(pos=(0, 0), size=(WIDTH, GROUND_HEIGHT)) + Color(*get_color_from_hex("#c17e45")) + Rectangle(pos=(0, GROUND_HEIGHT), size=(WIDTH, 6)) + + # Bird + with self.canvas: + Color(1, 0.92, 0.23) + Ellipse(pos=(BIRD_X, self.bird_y), size=(BIRD_SIZE, BIRD_SIZE)) + + self.score_label.text = str(self.score) + + def end_game(self): + self.state = "gameover" + Clock.unschedule(self.update) + if self.score > self.high_score: + self.high_score = self.score + save_highscore(self.high_score) + self.overlay_label.text = ( + f"[b]GAME OVER[/b]\nScore: {self.score} High: {self.high_score}\n" + "Press SPACE / Click / R to Restart" + ) + self.overlay_label.markup = True + + def flap(self): + if self.state == "menu": + self.start_game() + elif self.state == "playing": + self.bird_v = FLAP_V + elif self.state == "paused": + self.state = "playing" + self.overlay_label.text = "" + self.last_time = time.time() + Clock.schedule_interval(self.update, FPS) + elif self.state == "gameover": + self.start_game() + + def toggle_pause(self): + if self.state == "playing": + self.state = "paused" + Clock.unschedule(self.update) + self.overlay_label.text = "[b]PAUSED[/b]" + self.overlay_label.markup = True + elif self.state == "paused": + self.state = "playing" + self.overlay_label.text = "" + self.last_time = time.time() + Clock.schedule_interval(self.update, FPS) + + def restart(self): + self.start_game() + + def _on_key_down(self, window, key, *_): + if key in (32, 273): # Space / Up + self.flap() + elif key in (112, 80): # P + self.toggle_pause() + elif key in (114, 82): # R + self.restart() + + def _on_mouse_down(self, window, x, y, button, *_): + if button == "left": + self.flap() + +class FlappyApp(App): + def build(self): + return FlappyGame() + +if __name__ == "__main__": + FlappyApp().run() diff --git a/flappy_bird/flappy_highscore.json b/flappy_bird/flappy_highscore.json new file mode 100644 index 0000000..8338a01 --- /dev/null +++ b/flappy_bird/flappy_highscore.json @@ -0,0 +1 @@ +{"high_score": 17} \ No newline at end of file diff --git a/hangman/hangman.py b/hangman/hangman.py new file mode 100644 index 0000000..78256e8 --- /dev/null +++ b/hangman/hangman.py @@ -0,0 +1,461 @@ +import tkinter as tk +from tkinter import ttk, messagebox +import random +import json +import os +from datetime import datetime + +SAVE_FILE = "hangman_save.json" +WORDS_FILE = "words.txt" + +# Built-in fallback word list (English, mixed lengths) +FALLBACK_WORDS = [ + "python","kotlin","variable","function","class","inheritance","encapsulation","polymorphism", + "recursion","iterator","generator","algorithm","database","network","protocol","compiler", + "interpreter","abstraction","optimization","synchronization","asynchronous","pipeline","container", + "framework","library","package","virtualenv","repository","encryption","decryption","signature", + "blockchain","consensus","throughput","latency","bandwidth","parallel","concurrency","scalability", + "interface","component","architecture","microservice","refactor","testing","coverage","mocking", + "decorator","singleton","observer","strategy","factory","builder","prototype","adapter","bridge", + "facade","mediator","proxy","command","state","visitor","iterator","memento","flyweight", + "gradient","backpropagation","neuron","activation","regularization","dropout","optimizer","dataset", + "feature","label","cluster","regression","classification","pipeline","tokenizer","embedding", + "attention","transformer","sequence","temporal","spatial","graph","topology","metadata","index", + "cursor","trigger","materialized","transaction","isolation","consistency","durability","availability", + "idempotent","deterministic","stochastic","heuristic","approximation","quantization","sampling" +] + +DIFFICULTY_RULES = { + "Easy": {"min_len": 4, "max_len": 7, "max_attempts": 8}, + "Normal": {"min_len": 6, "max_len": 12, "max_attempts": 7}, + "Hard": {"min_len": 8, "max_len": 20, "max_attempts": 6}, +} + +class HangmanGame: + def __init__(self, words): + self.words = words + self.secret_word = "" + self.display = [] + self.wrong_letters = set() + self.correct_letters = set() + self.max_attempts = 7 + self.hints_left = 1 + + def start_new(self, difficulty: str): + rules = DIFFICULTY_RULES.get(difficulty, DIFFICULTY_RULES["Normal"]) + self.max_attempts = rules["max_attempts"] + candidates = [w.lower() for w in self.words if rules["min_len"] <= len(w) <= rules["max_len"] and w.isalpha()] + if not candidates: + candidates = FALLBACK_WORDS + self.secret_word = random.choice(candidates).lower() + self.display = ["_" if ch.isalpha() else ch for ch in self.secret_word] + self.wrong_letters = set() + self.correct_letters = set() + self.hints_left = 1 + + def guess(self, ch: str): + ch = ch.lower() + if not ch.isalpha() or len(ch) != 1: + return "invalid" + if ch in self.correct_letters or ch in self.wrong_letters: + return "repeat" + if ch in self.secret_word: + self.correct_letters.add(ch) + for i, c in enumerate(self.secret_word): + if c == ch: + self.display[i] = ch + return "hit" + else: + self.wrong_letters.add(ch) + return "miss" + + def use_hint(self): + if self.hints_left <= 0: + return False, None + hidden_indices = [i for i, c in enumerate(self.display) if c == "_"] + if not hidden_indices: + return False, None + idx = random.choice(hidden_indices) + letter = self.secret_word[idx] + self.hints_left -= 1 + # Hint costs half a miss (rounded up) by consuming one attempt directly + # More punishing on Hard + self.wrong_letters.add("\u273f") # decorative marker for hint-usage as a 'miss' + for i, c in enumerate(self.secret_word): + if c == letter: + self.display[i] = letter + self.correct_letters.add(letter) + return True, letter + + def is_won(self): + return "_" not in self.display + + def is_lost(self): + return len([w for w in self.wrong_letters if w]) >= self.max_attempts + + def attempts_used(self): + return len([w for w in self.wrong_letters if w]) + + def attempts_left(self): + return max(0, self.max_attempts - self.attempts_used()) + + +class HangmanApp(tk.Tk): + def __init__(self): + super().__init__() + self.title("Hangman — Deluxe Tkinter") + self.geometry("900x600") + self.minsize(820, 560) + self.configure(bg="#0b1021") + self.columnconfigure(0, weight=1) + self.rowconfigure(0, weight=1) + + self.stats = { + "games": 0, + "wins": 0, + "streak": 0, + "best": 0, + "difficulty": "Normal", + "last_played": None, + } + + self._load_stats() + self.words = self._load_words() + self.game = HangmanGame(self.words) + + self.style = ttk.Style(self) + self._setup_style() + self._build_ui() + self.new_game() + + self.bind("", self._on_keypress) + + # ------------------ Persistence ------------------ + def _load_stats(self): + if os.path.exists(SAVE_FILE): + try: + with open(SAVE_FILE, "r", encoding="utf-8") as f: + self.stats = json.load(f) + except Exception: + pass + + def _save_stats(self): + self.stats["last_played"] = datetime.now().isoformat(timespec="seconds") + try: + with open(SAVE_FILE, "w", encoding="utf-8") as f: + json.dump(self.stats, f, indent=2) + except Exception: + pass + + def _load_words(self): + if os.path.exists(WORDS_FILE): + try: + with open(WORDS_FILE, "r", encoding="utf-8") as f: + words = [w.strip() for w in f if w.strip() and w.strip().isalpha()] + if len(words) >= 50: + return words + except Exception: + pass + return FALLBACK_WORDS + + # ------------------ UI Setup ------------------ + def _setup_style(self): + # Dark theme styling + self.style.theme_use("clam") + self.style.configure("TFrame", background="#0b1021") + self.style.configure("Header.TLabel", background="#0b1021", foreground="#e2e8f0", font=("Segoe UI", 18, "bold")) + self.style.configure("Sub.TLabel", background="#0b1021", foreground="#a8b0c2", font=("Segoe UI", 11)) + self.style.configure("Word.TLabel", background="#0b1021", foreground="#f8fafc", font=("JetBrains Mono", 28, "bold")) + self.style.configure("Info.TLabel", background="#0b1021", foreground="#9fb3ff", font=("Segoe UI", 11, "bold")) + self.style.configure("Stat.TLabel", background="#0b1021", foreground="#cbd5e1", font=("Segoe UI", 10)) + self.style.configure("TButton", font=("Segoe UI", 10, "bold"), padding=8) + self.style.map("TButton", foreground=[('disabled', '#777')]) + + def _build_ui(self): + container = ttk.Frame(self) + container.grid(row=0, column=0, sticky="nsew", padx=16, pady=16) + container.columnconfigure(0, weight=1) + container.rowconfigure(1, weight=1) + + # Header + header = ttk.Frame(container) + header.grid(row=0, column=0, sticky="ew", pady=(0, 12)) + header.columnconfigure(1, weight=1) + ttk.Label(header, text="Hangman", style="Header.TLabel").grid(row=0, column=0, sticky="w") + self.status_label = ttk.Label(header, text="", style="Sub.TLabel") + self.status_label.grid(row=1, column=0, sticky="w") + + # Controls on the right + ctrl = ttk.Frame(header) + ctrl.grid(row=0, column=2, rowspan=2, sticky="e") + self.diff_var = tk.StringVar(value=self.stats.get("difficulty", "Normal")) + ttk.Label(ctrl, text="Difficulty", style="Sub.TLabel").grid(row=0, column=0, padx=(0,6)) + self.diff_cb = ttk.Combobox(ctrl, values=list(DIFFICULTY_RULES.keys()), textvariable=self.diff_var, state="readonly", width=8) + self.diff_cb.grid(row=0, column=1) + self.diff_cb.bind("<>", lambda e: self._on_change_difficulty()) + ttk.Button(ctrl, text="New Game", command=self.new_game).grid(row=0, column=2, padx=(10,0)) + self.hint_btn = ttk.Button(ctrl, text="Hint", command=self._hint) + self.hint_btn.grid(row=0, column=3, padx=(8,0)) + ttk.Button(ctrl, text="Reset Stats", command=self._reset_stats).grid(row=0, column=4, padx=(8,0)) + + # Main area: left canvas (gallows), right word + keyboard + stats + main = ttk.Frame(container) + main.grid(row=1, column=0, sticky="nsew") + main.columnconfigure(1, weight=1) + main.rowconfigure(1, weight=1) + + # Gallows canvas + self.canvas = tk.Canvas(main, width=320, height=360, bg="#0f1531", bd=0, highlightthickness=0) + self.canvas.grid(row=0, column=0, rowspan=3, sticky="nsw", padx=(0, 16)) + + # Word display + word_frame = ttk.Frame(main) + word_frame.grid(row=0, column=1, sticky="ew") + self.word_var = tk.StringVar(value="") + self.word_label = ttk.Label(word_frame, textvariable=self.word_var, style="Word.TLabel") + self.word_label.grid(row=0, column=0, sticky="w") + + # Info + wrong letters + info_frame = ttk.Frame(main) + info_frame.grid(row=1, column=1, sticky="ew", pady=(6, 6)) + self.attempts_var = tk.StringVar() + self.wrongs_var = tk.StringVar() + ttk.Label(info_frame, textvariable=self.attempts_var, style="Info.TLabel").grid(row=0, column=0, sticky="w") + ttk.Label(info_frame, textvariable=self.wrongs_var, style="Info.TLabel").grid(row=0, column=1, sticky="w", padx=(16,0)) + + # On-screen keyboard + kb = ttk.Frame(main) + kb.grid(row=2, column=1, sticky="nsew") + kb.columnconfigure(tuple(range(10)), weight=1) + rows = ["QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM"] + self.kb_buttons = {} + for r, letters in enumerate(rows): + row_frame = ttk.Frame(kb) + row_frame.grid(row=r, column=0, sticky="ew", pady=3) + # left padding for staggered look + pad = 0 if r == 0 else (20 if r == 1 else 42) + row_frame.grid_columnconfigure(0, minsize=pad) + for c, ch in enumerate(letters, start=1): + btn = ttk.Button(row_frame, text=ch, width=3, command=lambda x=ch: self._press(x)) + btn.grid(row=0, column=c, padx=3, pady=3) + self.kb_buttons[ch.lower()] = btn + + # Stats bar + stats = ttk.Frame(container) + stats.grid(row=2, column=0, sticky="ew", pady=(12,0)) + self.stats_var = tk.StringVar(value=self._format_stats()) + ttk.Label(stats, textvariable=self.stats_var, style="Stat.TLabel").grid(row=0, column=0, sticky="w") + + # ------------------ Interactions ------------------ + def _press(self, ch: str): + if not self._game_active: + return + outcome = self.game.guess(ch) + if outcome == "invalid" or outcome == "repeat": + self._pulse(self.status_label, text="Already used" if outcome=="repeat" else "Type A–Z only") + return + self._update_ui_after_guess(ch) + + def _on_keypress(self, event): + ch = event.char + if ch: + self._press(ch.upper()) + + def _update_ui_after_guess(self, ch: str): + # Disable keyboard button + btn = self.kb_buttons.get(ch.lower()) + if btn: + btn.state(["disabled"]) # disable button + # Update labels + self.word_var.set(self._spaced_word()) + self._update_info() + self._draw_hangman() + # Flash letter in word if hit + if ch.lower() in self.game.secret_word: + self._flash(self.word_label) + else: + self._shake(self.canvas) + # Check end state + if self.game.is_won(): + self._end_game(won=True) + elif self.game.is_lost(): + self._end_game(won=False) + + def _hint(self): + if not self._game_active: + return + ok, letter = self.game.use_hint() + if not ok: + self._pulse(self.status_label, text="No hints left") + return + # Disable the revealed letter's key, too + btn = self.kb_buttons.get(letter) + if btn: + btn.state(["disabled"]) + self.word_var.set(self._spaced_word()) + self._update_info() + self._draw_hangman() + self._flash(self.word_label) + if self.game.is_won(): + self._end_game(won=True) + + def _on_change_difficulty(self): + self.stats["difficulty"] = self.diff_var.get() + self._save_stats() + self.new_game() + + def _reset_stats(self): + if messagebox.askyesno("Reset Stats", "Reset all stats (games, wins, streaks)?"): + self.stats = { + "games": 0, "wins": 0, "streak": 0, "best": 0, + "difficulty": self.diff_var.get(), "last_played": None, + } + self._save_stats() + self.stats_var.set(self._format_stats()) + self._pulse(self.status_label, text="Stats reset") + + # ------------------ Game Flow ------------------ + def new_game(self): + self._game_active = True + self.canvas.delete("all") + self.status_label.configure(text=f"Good luck! Difficulty: {self.diff_var.get()}") + self.game.start_new(self.diff_var.get()) + self.word_var.set(self._spaced_word()) + self._update_info() + for b in self.kb_buttons.values(): + b.state(["!disabled"]) + self.hint_btn.state(["!disabled"]) if self.game.hints_left > 0 else self.hint_btn.state(["disabled"]) + self._draw_gallows() + + def _end_game(self, won: bool): + self._game_active = False + self.stats["games"] += 1 + if won: + self.stats["wins"] += 1 + self.stats["streak"] += 1 + self.stats["best"] = max(self.stats["best"], self.stats["streak"]) + self.status_label.configure(text="You win! \U0001F389") + self._celebrate() + else: + self.stats["streak"] = 0 + self.status_label.configure(text=f"You lost. Word was: {self.game.secret_word.upper()}") + self._shake(self.word_label) + self._save_stats() + self.stats_var.set(self._format_stats()) + for b in self.kb_buttons.values(): + b.state(["disabled"]) + self.hint_btn.state(["disabled"]) + + # ------------------ UI Helpers ------------------ + def _format_stats(self): + g = self.stats.get("games", 0) + w = self.stats.get("wins", 0) + rate = (w / g * 100) if g else 0.0 + return ( + f"Games: {g} Wins: {w} Win%: {rate:.1f}% " + f"Streak: {self.stats.get('streak',0)} Best: {self.stats.get('best',0)} " + f"Difficulty: {self.stats.get('difficulty','Normal')}" + ) + + def _spaced_word(self): + return " ".join(ch.upper() for ch in self.game.display) + + def _update_info(self): + self.attempts_var.set(f"Attempts: {self.game.attempts_used()}/{self.game.max_attempts} (left: {self.game.attempts_left()})") + wrongs = [w for w in self.game.wrong_letters if w and w != "\u273f"] + extra = " | hint used" if "\u273f" in self.game.wrong_letters else "" + self.wrongs_var.set(f"Wrong: {' '.join(sorted(w.upper() for w in wrongs))}{extra}") + + # ------------------ Drawing ------------------ + def _draw_gallows(self): + c = self.canvas + w, h = int(c["width"]), int(c["height"]) + c.create_rectangle(20, h-20, w-20, h-10, fill="#182046", outline="") # ground + c.create_line(60, h-20, 60, 40, width=6, fill="#9fb3ff") + c.create_line(60, 40, 200, 40, width=6, fill="#9fb3ff") + c.create_line(200, 40, 200, 80, width=4, fill="#9fb3ff") + + def _draw_hangman(self): + # Draw parts based on attempts used + c = self.canvas + c.delete("hang") + a = self.game.attempts_used() + # 1: head + if a >= 1: + c.create_oval(170, 80, 230, 140, outline="#e2e8f0", width=3, tags="hang") + # 2: body + if a >= 2: + c.create_line(200, 140, 200, 220, fill="#e2e8f0", width=3, tags="hang") + # 3: left arm + if a >= 3: + c.create_line(200, 160, 165, 190, fill="#e2e8f0", width=3, tags="hang") + # 4: right arm + if a >= 4: + c.create_line(200, 160, 235, 190, fill="#e2e8f0", width=3, tags="hang") + # 5: left leg + if a >= 5: + c.create_line(200, 220, 175, 270, fill="#e2e8f0", width=3, tags="hang") + # 6: right leg + if a >= 6: + c.create_line(200, 220, 225, 270, fill="#e2e8f0", width=3, tags="hang") + # 7: face X + if a >= 7: + c.create_line(185, 100, 195, 110, fill="#e2e8f0", width=2, tags="hang") + c.create_line(185, 110, 195, 100, fill="#e2e8f0", width=2, tags="hang") + c.create_line(205, 100, 215, 110, fill="#e2e8f0", width=2, tags="hang") + c.create_line(205, 110, 215, 100, fill="#e2e8f0", width=2, tags="hang") + c.create_line(188, 125, 212, 125, fill="#e2e8f0", width=2, tags="hang") + + # ------------------ Micro-animations ------------------ + def _flash(self, widget, times: int = 2, interval: int = 120): + orig = widget.cget("foreground") if isinstance(widget, ttk.Label) else None + def step(i): + if i <= 0: + if orig is not None: + widget.configure(foreground="#f8fafc") + return + if orig is not None: + widget.configure(foreground="#91ffb1") + self.after(interval, lambda: (orig is not None) and widget.configure(foreground="#f8fafc") or None) + self.after(interval*2, lambda: step(i-1)) + step(times) + + def _pulse(self, widget, text=None, times: int = 2, interval: int = 120): + if text is not None: + widget.configure(text=text) + def step(i): + if i <= 0: + return + widget.configure(foreground="#ffffff") + self.after(interval, lambda: widget.configure(foreground="#a8b0c2")) + self.after(interval*2, lambda: step(i-1)) + step(times) + + def _shake(self, widget, distance: int = 8, shakes: int = 6, interval: int = 20): + # Applies to canvas or label by moving with place or canvas move + if isinstance(widget, tk.Canvas): + target = widget + def step(i, dir): + if i <= 0: + return + target.move("all", dir*distance, 0) + self.after(interval, lambda: (target.move("all", -dir*distance, 0), step(i-1, -dir))) + step(shakes, 1) + else: + # For labels: use place inside a frame? Simpler: flash instead + self._flash(widget) + + def _celebrate(self): + # Simple confetti dots on canvas + c = self.canvas + for i in range(60): + x = random.randint(40, 300) + y = random.randint(40, 300) + r = random.randint(2, 5) + c.create_oval(x-r, y-r, x+r, y+r, fill="#91ffb1", outline="", tags="celebrate") + self.after(900, lambda: c.delete("celebrate")) + + +if __name__ == "__main__": + app = HangmanApp() + app.mainloop() diff --git a/hangman/hangman_mobile.py b/hangman/hangman_mobile.py new file mode 100644 index 0000000..b0dd246 --- /dev/null +++ b/hangman/hangman_mobile.py @@ -0,0 +1,225 @@ +from kivy.app import App +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.gridlayout import GridLayout +from kivy.uix.label import Label +from kivy.uix.button import Button +from kivy.uix.widget import Widget +from kivy.graphics import Color, Line, Ellipse, Rectangle +from kivy.properties import StringProperty, NumericProperty +from kivy.clock import Clock +import random +import json +import os +from datetime import datetime + +SAVE_FILE = "hangman_save.json" +WORDS_FILE = "words.txt" + +FALLBACK_WORDS = [ + "python","kotlin","variable","function","class","inheritance","encapsulation","polymorphism", + "recursion","iterator","generator","algorithm","database","network","protocol","compiler", + "interpreter","abstraction","optimization","synchronization","asynchronous","pipeline","container", +] + +DIFFICULTY_RULES = { + "Easy": {"min_len": 4, "max_len": 7, "max_attempts": 8}, + "Normal": {"min_len": 6, "max_len": 12, "max_attempts": 7}, + "Hard": {"min_len": 8, "max_len": 20, "max_attempts": 6}, +} + +class HangmanGame: + def __init__(self, words): + self.words = words + self.secret_word = "" + self.display = [] + self.wrong_letters = set() + self.correct_letters = set() + self.max_attempts = 7 + self.hints_left = 1 + + def start_new(self, difficulty: str): + rules = DIFFICULTY_RULES.get(difficulty, DIFFICULTY_RULES["Normal"]) + self.max_attempts = rules["max_attempts"] + candidates = [w.lower() for w in self.words if rules["min_len"] <= len(w) <= rules["max_len"] and w.isalpha()] + if not candidates: + candidates = FALLBACK_WORDS + self.secret_word = random.choice(candidates) + self.display = ["_" if c.isalpha() else c for c in self.secret_word] + self.wrong_letters = set() + self.correct_letters = set() + self.hints_left = 1 + + def guess(self, ch: str): + ch = ch.lower() + if not ch.isalpha() or len(ch) != 1: + return "invalid" + if ch in self.correct_letters or ch in self.wrong_letters: + return "repeat" + if ch in self.secret_word: + self.correct_letters.add(ch) + for i, c in enumerate(self.secret_word): + if c == ch: + self.display[i] = ch + return "hit" + else: + self.wrong_letters.add(ch) + return "miss" + + def use_hint(self): + if self.hints_left <= 0: + return False, None + hidden = [i for i, c in enumerate(self.display) if c == "_"] + if not hidden: + return False, None + idx = random.choice(hidden) + letter = self.secret_word[idx] + self.hints_left -= 1 + self.wrong_letters.add("\u273f") # hint as 'miss' + for i, c in enumerate(self.secret_word): + if c == letter: + self.display[i] = letter + self.correct_letters.add(letter) + return True, letter + + def is_won(self): + return "_" not in self.display + + def is_lost(self): + return len([w for w in self.wrong_letters if w]) >= self.max_attempts + + def attempts_used(self): + return len([w for w in self.wrong_letters if w]) + + def attempts_left(self): + return max(0, self.max_attempts - self.attempts_used()) + + +class HangmanCanvas(Widget): + attempts = NumericProperty(0) + + def on_attempts(self, instance, value): + self.canvas.clear() + w, h = self.width, self.height + with self.canvas: + Color(0.6, 0.7, 1) + # Gallows + Line(points=[60, 20, 60, h-40], width=6) + Line(points=[60, h-40, w-60, h-40], width=6) + Line(points=[w-60, h-40, w-60, h-100], width=4) + # Hangman parts + if value >= 1: + Ellipse(pos=(w-80, h-140, 60, 60)) + if value >= 2: + Line(points=[w-50, h-140, w-50, h-240], width=3) + if value >= 3: + Line(points=[w-50, h-180, w-80, h-220], width=3) + if value >= 4: + Line(points=[w-50, h-180, w-20, h-220], width=3) + if value >= 5: + Line(points=[w-50, h-240, w-80, h-290], width=3) + if value >= 6: + Line(points=[w-50, h-240, w-20, h-290], width=3) + if value >= 7: + Line(points=[w-75, h-125, w-65, h-115], width=2) + Line(points=[w-75, h-115, w-65, h-125], width=2) + +class HangmanAppUI(BoxLayout): + word_text = StringProperty("") + info_text = StringProperty("") + wrong_text = StringProperty("") + status_text = StringProperty("") + + def __init__(self, **kwargs): + super().__init__(orientation="vertical", spacing=10, padding=10, **kwargs) + self.stats = {"games":0, "wins":0, "streak":0, "best":0, "difficulty":"Normal", "last_played":None} + self._load_stats() + self.words = self._load_words() + self.game = HangmanGame(self.words) + self._game_active = True + + # Top: Status & controls + top = BoxLayout(size_hint_y=None, height=40, spacing=5) + self.status_lbl = Label(text=self.status_text) + self.diff_btn = Button(text="New Game", size_hint_x=None, width=100, on_press=lambda x: self.new_game()) + top.add_widget(self.status_lbl) + top.add_widget(self.diff_btn) + self.add_widget(top) + + # Middle: Hangman + word + middle = BoxLayout() + self.hangman_canvas = HangmanCanvas(size_hint=(0.4,1)) + middle.add_widget(self.hangman_canvas) + self.word_lbl = Label(text=self.word_text, font_size=40) + middle.add_widget(self.word_lbl) + self.add_widget(middle) + + # Keyboard + kb = GridLayout(cols=10, size_hint_y=None, height=200, spacing=2) + self.kb_buttons = {} + for ch in "QWERTYUIOPASDFGHJKLZXCVBNM": + btn = Button(text=ch, on_press=lambda b, x=ch: self.press(x)) + kb.add_widget(btn) + self.kb_buttons[ch.lower()] = btn + self.add_widget(kb) + + self.new_game() + + def _load_stats(self): + if os.path.exists(SAVE_FILE): + try: + with open(SAVE_FILE, "r") as f: + self.stats.update(json.load(f)) + except: pass + + def _save_stats(self): + self.stats["last_played"] = datetime.now().isoformat(timespec="seconds") + with open(SAVE_FILE, "w") as f: + json.dump(self.stats, f, indent=2) + + def _load_words(self): + if os.path.exists(WORDS_FILE): + try: + with open(WORDS_FILE,"r") as f: + words = [w.strip() for w in f if w.strip().isalpha()] + if len(words)>=50: return words + except: pass + return FALLBACK_WORDS + + def press(self, ch): + if not self._game_active: return + outcome = self.game.guess(ch) + self.update_ui() + self.kb_buttons[ch.lower()].disabled = True + if self.game.is_won(): + self.end_game(True) + elif self.game.is_lost(): + self.end_game(False) + + def new_game(self): + self._game_active = True + self.game.start_new("Normal") + for b in self.kb_buttons.values(): b.disabled=False + self.update_ui() + + def update_ui(self): + self.word_text = " ".join(self.game.display).upper() + self.word_lbl.text = self.word_text + self.hangman_canvas.attempts = self.game.attempts_used() + + def end_game(self, won): + self._game_active = False + self.stats["games"] +=1 + if won: + self.stats["wins"] +=1 + self.stats["streak"] +=1 + else: + self.stats["streak"]=0 + self._save_stats() + + +class HangmanApp(App): + def build(self): + return HangmanAppUI() + +if __name__=="__main__": + HangmanApp().run() diff --git a/mobile-stone-paper-scissors.py b/mobile-stone-paper-scissors.py new file mode 100644 index 0000000..940dc97 --- /dev/null +++ b/mobile-stone-paper-scissors.py @@ -0,0 +1,166 @@ +from kivy.app import App +from kivy.lang import Builder +from kivy.uix.screenmanager import ScreenManager, Screen +from kivy.properties import StringProperty, NumericProperty, ListProperty +from kivy.uix.behaviors import ButtonBehavior +from kivy.uix.image import Image +from datetime import datetime +import random +import json +import os + + +class ImageButton(ButtonBehavior, Image): + pass + + +MOVES = {'R': 'Rock', 'P': 'Paper', 'S': 'Scissors'} + + +class HomeScreen(Screen): + pass + + +class GameScreen(Screen): + result_text = StringProperty("") + result_bg_color = ListProperty([1, 1, 1, 1]) + + wins = NumericProperty(0) + losses = NumericProperty(0) + ties = NumericProperty(0) + streak = NumericProperty(0) + + def on_pre_enter(self): + self.load_data() + self.update_stats() + + def adaptive_ai(self): + if len(self.manager.history) < 3: + return random.choice(['R', 'P', 'S']) + last_moves = [h['player'] for h in self.manager.history[-3:]] + most_common = max(set(last_moves), key=last_moves.count) + counter = {'R': 'P', 'P': 'S', 'S': 'R'} + return counter[most_common] + + def play(self, user_move): + computer_move = self.adaptive_ai() + result = self.determine_winner(user_move, computer_move) + self.manager.history.append({ + 'time': datetime.now().strftime("%Y-%m-%d %H:%M"), + 'player': user_move, + 'computer': computer_move, + 'result': result + }) + + feedback = { + 'win': ("You Win!", [0.36, 0.85, 0.35, 1]), + 'loss': ("Computer Wins!", [0.95, 0.42, 0.42, 1]), + 'tie': ("It's a Draw!", [0.99, 0.85, 0.24, 1]) + } + text, color = feedback[result] + self.result_text = f"{MOVES[user_move]} vs {MOVES[computer_move]} — {text}" + self.result_bg_color = color + self.update_stats() + self.save_data() + + def determine_winner(self, player, computer): + if player == computer: + self.ties += 1 + self.streak = 0 + return 'tie' + elif (player == 'R' and computer == 'S') or \ + (player == 'P' and computer == 'R') or \ + (player == 'S' and computer == 'P'): + self.wins += 1 + self.streak += 1 + return 'win' + else: + self.losses += 1 + self.streak = 0 + return 'loss' + + def update_stats(self): + total = self.wins + self.losses + self.ties + win_rate = (self.wins / total * 100) if total > 0 else 0 + self.ids.stats_label.text = f"Wins: {self.wins} Losses: {self.losses} Ties: {self.ties}\nStreak: {self.streak} Win Rate: {win_rate:.1f}%" + + def load_data(self): + if os.path.exists('rps_kivy_save.json'): + try: + with open('rps_kivy_save.json', 'r') as f: + data = json.load(f) + self.wins = data.get('wins', 0) + self.losses = data.get('losses', 0) + self.ties = data.get('ties', 0) + self.streak = data.get('streak', 0) + self.manager.history = data.get('history', []) + except: + pass + + def save_data(self): + data = { + 'wins': self.wins, + 'losses': self.losses, + 'ties': self.ties, + 'streak': self.streak, + 'history': self.manager.history + } + with open('rps_kivy_save.json', 'w') as f: + json.dump(data, f) + + def reset_game(self): + self.wins = self.losses = self.ties = self.streak = 0 + self.manager.history = [] + self.update_stats() + self.result_text = "" + self.result_bg_color = [1, 1, 1, 1] + self.save_data() + + +class HistoryScreen(Screen): + def on_pre_enter(self): + self.ids.history_text.text = "\n".join([ + f"{h['time']} | {MOVES[h['player']]} vs {MOVES[h['computer']]} : {h['result'].capitalize()}" + for h in self.manager.history[-10:] + ]) or "No history yet." + + +class LeaderboardScreen(Screen): + def on_pre_enter(self): + gs = self.manager.get_screen('game') + max_streak = 0 + current_streak = 0 + for h in self.manager.history: + if h['result'] == 'win': + current_streak += 1 + max_streak = max(max_streak, current_streak) + else: + current_streak = 0 + total = len(self.manager.history) + win_rate = (gs.wins / total * 100) if total > 0 else 0 + + self.ids.leaderboard_label.text = f""" +Total Games: {total} +Wins: {gs.wins} +Losses: {gs.losses} +Ties: {gs.ties} +Longest Win Streak: {max_streak} +Current Streak: {gs.streak} +Win Rate: {win_rate:.1f}% + """ + + +class RPSApp(App): + def build(self): + Builder.load_file('rps.kv') + sm = ScreenManager() + sm.history = [] + sm.add_widget(HomeScreen(name='home')) + sm.add_widget(GameScreen(name='game')) + sm.add_widget(HistoryScreen(name='history')) + sm.add_widget(LeaderboardScreen(name='leaderboard')) + return sm + + +if __name__ == '__main__': + RPSApp().run() diff --git a/rps.kv b/rps.kv new file mode 100644 index 0000000..d524c63 --- /dev/null +++ b/rps.kv @@ -0,0 +1,164 @@ +#:import Window kivy.core.window.Window +#:import dp kivy.metrics.dp +#:import FadeTransition kivy.uix.screenmanager.FadeTransition +#:import ImageButton mobile-stone-paper-scissors.ImageButton + +ScreenManager: + transition: FadeTransition() + HomeScreen: + GameScreen: + HistoryScreen: + LeaderboardScreen: + +: + name: "home" + BoxLayout: + orientation: 'vertical' + padding: dp(20) + spacing: dp(20) + canvas.before: + Color: + rgba: .1, .1, .1, 1 + Rectangle: + pos: self.pos + size: self.size + Label: + text: "Rock Paper Scissors" + font_size: Window.width / 15 + color: 1, 1, 1, 1 + Button: + text: "Start Game" + font_size: Window.width / 25 + size_hint_y: None + height: dp(60) + on_press: root.manager.current = "game" + Button: + text: "Quit" + font_size: Window.width / 25 + size_hint_y: None + height: dp(60) + on_press: app.stop() + +: + name: "game" + BoxLayout: + orientation: 'vertical' + padding: dp(20) + spacing: dp(20) + canvas.before: + Color: + rgba: 0.05, 0.05, 0.05, 1 + Rectangle: + pos: self.pos + size: self.size + Label: + id: stats_label + text: "" + font_size: Window.width / 30 + color: 1, 1, 1, 1 + GridLayout: + cols: 3 + spacing: dp(15) + size_hint_y: None + height: dp(180) + + ImageButton: + source: "assets/rock.png" + on_release: root.play('R') + + ImageButton: + source: "assets/paper.png" + on_release: root.play('P') + + ImageButton: + source: "assets/scissors.png" + on_release: root.play('S') + + Label: + text: root.result_text + font_size: Window.width / 30 + color: 1, 1, 1, 1 + size_hint_y: None + height: dp(120) + canvas.before: + Color: + rgba: root.result_bg_color + RoundedRectangle: + pos: self.pos + size: self.size + radius: [20,] + BoxLayout: + size_hint_y: None + height: dp(50) + spacing: dp(10) + Button: + text: "History" + on_press: root.manager.current = "history" + Button: + text: "Leaderboard" + on_press: root.manager.current = "leaderboard" + Button: + text: "Reset" + on_press: root.reset_game() + Button: + text: "Back to Home" + size_hint_y: None + height: dp(50) + on_press: root.manager.current = "home" + +: + name: "history" + BoxLayout: + orientation: 'vertical' + padding: dp(20) + spacing: dp(20) + canvas.before: + Color: + rgba: 0.1, 0.1, 0.1, 1 + Rectangle: + pos: self.pos + size: self.size + Label: + text: "Last 10 Matches" + font_size: Window.width / 20 + color: 1, 1, 1, 1 + ScrollView: + Label: + id: history_text + size_hint_y: None + height: self.texture_size[1] + text_size: self.width, None + font_size: Window.width / 35 + color: 1, 1, 1, 1 + Button: + text: "Back" + size_hint_y: None + height: dp(50) + on_press: root.manager.current = "game" + +: + name: "leaderboard" + BoxLayout: + orientation: 'vertical' + padding: dp(20) + spacing: dp(20) + canvas.before: + Color: + rgba: 0.1, 0.1, 0.1, 1 + Rectangle: + pos: self.pos + size: self.size + Label: + text: "Leaderboard" + font_size: Window.width / 20 + color: 1, 1, 1, 1 + Label: + id: leaderboard_label + text: "" + font_size: Window.width / 35 + color: 1, 1, 1, 1 + Button: + text: "Back" + size_hint_y: None + height: dp(50) + on_press: root.manager.current = "game" diff --git a/rps_kivy_save.json b/rps_kivy_save.json new file mode 100644 index 0000000..4fc9fe9 --- /dev/null +++ b/rps_kivy_save.json @@ -0,0 +1 @@ +{"wins": 1, "losses": 1, "ties": 0, "streak": 1, "history": [{"time": "2025-07-22 21:56", "player": "P", "computer": "S", "result": "loss"}, {"time": "2025-07-22 21:57", "player": "R", "computer": "S", "result": "win"}]} \ No newline at end of file diff --git a/snake game/snake_game.py b/snake game/snake_game.py new file mode 100644 index 0000000..d8934e0 --- /dev/null +++ b/snake game/snake_game.py @@ -0,0 +1,160 @@ +import tkinter as tk +import random +import json +import os + +CELL_SIZE = 20 +GRID_WIDTH = 25 +GRID_HEIGHT = 25 +UPDATE_DELAY = 100 # ms + +SAVE_FILE = 'snake_game_save.json' + +class SnakeGame: + def __init__(self, root): + self.root = root + self.root.title("🐍 Snake Game Deluxe 🐍") + self.root.resizable(False, False) + + self.canvas = tk.Canvas(root, width=CELL_SIZE * GRID_WIDTH, + height=CELL_SIZE * GRID_HEIGHT, bg="#1e1e1e", bd=0, highlightthickness=0) + self.canvas.pack() + + self.score = 0 + self.high_score = 0 + self.snake = [(5, 5)] + self.direction = (1, 0) + self.food = None + self.game_running = False + + self.load_data() + self.draw_ui() + self.reset_game() + + self.root.bind("", self.change_direction) + + def draw_ui(self): + self.top_frame = tk.Frame(self.root, bg="#282c34") + self.top_frame.pack(fill="x") + + self.score_label = tk.Label(self.top_frame, text="Score: 0", fg="white", bg="#282c34", + font=("Consolas", 14, "bold")) + self.score_label.pack(side="left", padx=10) + + self.high_score_label = tk.Label(self.top_frame, text=f"High Score: {self.high_score}", fg="white", + bg="#282c34", font=("Consolas", 14, "bold")) + self.high_score_label.pack(side="right", padx=10) + + self.reset_button = tk.Button(self.top_frame, text="🔁 Restart", command=self.reset_game, + bg="#61afef", fg="white", font=("Consolas", 12, "bold"), bd=0, relief="flat") + self.reset_button.pack(side="left", padx=10) + + def load_data(self): + if os.path.exists(SAVE_FILE): + try: + with open(SAVE_FILE, 'r') as f: + data = json.load(f) + self.high_score = data.get("high_score", 0) + except: + self.high_score = 0 + + def save_data(self): + with open(SAVE_FILE, 'w') as f: + json.dump({"high_score": self.high_score}, f) + + def reset_game(self): + self.snake = [(5, 5)] + self.direction = (1, 0) + self.score = 0 + self.game_running = True + self.spawn_food() + self.update_ui() + self.game_loop() + + def spawn_food(self): + while True: + x = random.randint(0, GRID_WIDTH - 1) + y = random.randint(0, GRID_HEIGHT - 1) + if (x, y) not in self.snake: + self.food = (x, y) + break + + def change_direction(self, event): + key = event.keysym + new_dir = { + 'Up': (0, -1), + 'Down': (0, 1), + 'Left': (-1, 0), + 'Right': (1, 0) + }.get(key) + + if new_dir: + opposite = (-self.direction[0], -self.direction[1]) + if new_dir != opposite: # Prevent reversing + self.direction = new_dir + + def game_loop(self): + if not self.game_running: + return + + head_x, head_y = self.snake[-1] + dx, dy = self.direction + new_head = (head_x + dx, head_y + dy) + + if (new_head in self.snake or + new_head[0] < 0 or new_head[0] >= GRID_WIDTH or + new_head[1] < 0 or new_head[1] >= GRID_HEIGHT): + self.end_game() + return + + self.snake.append(new_head) + + if new_head == self.food: + self.score += 1 + self.spawn_food() + else: + self.snake.pop(0) + + self.update_ui() + self.root.after(UPDATE_DELAY, self.game_loop) + + def update_ui(self): + self.canvas.delete("all") + + # Draw snake + for segment in self.snake: + x1 = segment[0] * CELL_SIZE + y1 = segment[1] * CELL_SIZE + x2 = x1 + CELL_SIZE + y2 = y1 + CELL_SIZE + self.canvas.create_rectangle(x1, y1, x2, y2, fill="#98c379", outline="#1e1e1e") + + # Draw head with highlight + hx, hy = self.snake[-1] + self.canvas.create_rectangle(hx * CELL_SIZE, hy * CELL_SIZE, + hx * CELL_SIZE + CELL_SIZE, hy * CELL_SIZE + CELL_SIZE, + fill="#e06c75", outline="#1e1e1e") + + # Draw food + fx, fy = self.food + self.canvas.create_oval(fx * CELL_SIZE, fy * CELL_SIZE, + fx * CELL_SIZE + CELL_SIZE, fy * CELL_SIZE + CELL_SIZE, + fill="#d19a66", outline="#1e1e1e") + + self.score_label.config(text=f"Score: {self.score}") + + def end_game(self): + self.game_running = False + if self.score > self.high_score: + self.high_score = self.score + self.save_data() + self.high_score_label.config(text=f"High Score: {self.high_score}") + self.canvas.create_text(CELL_SIZE * GRID_WIDTH // 2, + CELL_SIZE * GRID_HEIGHT // 2, + text="Game Over", fill="white", + font=("Consolas", 24, "bold")) + +if __name__ == "__main__": + root = tk.Tk() + app = SnakeGame(root) + root.mainloop() diff --git a/snake game/snake_game_mobile.py b/snake game/snake_game_mobile.py new file mode 100644 index 0000000..dac1810 --- /dev/null +++ b/snake game/snake_game_mobile.py @@ -0,0 +1,176 @@ +from kivy.app import App +from kivy.uix.widget import Widget +from kivy.uix.button import Button +from kivy.graphics import Color, Rectangle, Ellipse +from kivy.clock import Clock +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.label import Label +from kivy.core.window import Window +import random +import json +import os + +CELL_SIZE = 20 +GRID_WIDTH = 25 +GRID_HEIGHT = 25 +UPDATE_DELAY = 0.1 +SAVE_FILE = "snake_game_save.json" + + +class SnakeGame(Widget): + def __init__(self, score_label, high_score_label, **kwargs): + super().__init__(**kwargs) + self.score_label = score_label + self.high_score_label = high_score_label + + self.snake = [(5, 5)] + self.direction = (1, 0) + self.food = None + self.score = 0 + self.high_score = 0 + self.game_running = True + + self.load_data() + self.spawn_food() + self.update_canvas() + + Clock.schedule_interval(self.update, UPDATE_DELAY) + Window.bind(on_key_down=self.on_key_down) + + def load_data(self): + if os.path.exists(SAVE_FILE): + try: + with open(SAVE_FILE, 'r') as f: + data = json.load(f) + self.high_score = data.get("high_score", 0) + except: + self.high_score = 0 + + def save_data(self): + with open(SAVE_FILE, 'w') as f: + json.dump({"high_score": self.high_score}, f) + + def reset_game(self): + self.snake = [(5, 5)] + self.direction = (1, 0) + self.score = 0 + self.game_running = True + self.spawn_food() + self.update_canvas() + self.score_label.text = f"Score: {self.score}" + self.high_score_label.text = f"High Score: {self.high_score}" + + def spawn_food(self): + while True: + x = random.randint(0, GRID_WIDTH - 1) + y = random.randint(0, GRID_HEIGHT - 1) + if (x, y) not in self.snake: + self.food = (x, y) + break + + def on_key_down(self, window, key, *args): + key_map = { + 273: (0, 1), # Up + 274: (0, -1), # Down + 276: (-1, 0), # Left + 275: (1, 0) # Right + } + if key in key_map: + new_dir = key_map[key] + opposite = (-self.direction[0], -self.direction[1]) + if new_dir != opposite: + self.direction = new_dir + + def update(self, dt): + if not self.game_running: + return + + head_x, head_y = self.snake[-1] + dx, dy = self.direction + new_head = (head_x + dx, head_y + dy) + + if (new_head in self.snake or + new_head[0] < 0 or new_head[0] >= GRID_WIDTH or + new_head[1] < 0 or new_head[1] >= GRID_HEIGHT): + self.end_game() + return + + self.snake.append(new_head) + + if new_head == self.food: + self.score += 1 + self.spawn_food() + else: + self.snake.pop(0) + + self.update_canvas() + self.score_label.text = f"Score: {self.score}" + + def update_canvas(self): + self.canvas.clear() + with self.canvas: + # Snake body + for segment in self.snake[:-1]: + Color(0.6, 0.8, 0.5) + Rectangle(pos=(segment[0] * CELL_SIZE, segment[1] * CELL_SIZE), + size=(CELL_SIZE, CELL_SIZE)) + + # Snake head + head = self.snake[-1] + Color(1, 0.4, 0.4) + Rectangle(pos=(head[0] * CELL_SIZE, head[1] * CELL_SIZE), + size=(CELL_SIZE, CELL_SIZE)) + + # Food + Color(0.8, 0.6, 0.3) + Ellipse(pos=(self.food[0] * CELL_SIZE, self.food[1] * CELL_SIZE), + size=(CELL_SIZE, CELL_SIZE)) + + def end_game(self): + self.game_running = False + if self.score > self.high_score: + self.high_score = self.score + self.save_data() + + self.high_score_label.text = f"High Score: {self.high_score}" + self.score_label.text = f"Game Over! Final Score: {self.score}" + self.canvas.clear() + with self.canvas: + Color(1, 1, 1) + Rectangle(pos=(0, (GRID_HEIGHT * CELL_SIZE) // 2 - 20), + size=(GRID_WIDTH * CELL_SIZE, 40)) + + +class SnakeApp(App): + def build(self): + Window.size = (GRID_WIDTH * CELL_SIZE, GRID_HEIGHT * CELL_SIZE + 40) + main_layout = BoxLayout(orientation="vertical", padding=0, spacing=0) + + # Score bar + top_bar = BoxLayout(size_hint_y=None, height=40, padding=10, spacing=10) + self.score_label = Label(text="Score: 0", color=(1, 1, 1, 1), + bold=True, font_size=16) + self.high_score_label = Label(text="High Score: 0", color=(1, 1, 1, 1), + bold=True, font_size=16) + + restart_btn = Button(text="🔁 Restart", background_color=(0.2, 0.6, 1, 1), + color=(1, 1, 1, 1), bold=True) + restart_btn.bind(on_press=self.restart_game) + + top_bar.add_widget(self.score_label) + top_bar.add_widget(self.high_score_label) + top_bar.add_widget(restart_btn) + + # Game + self.game = SnakeGame(score_label=self.score_label, high_score_label=self.high_score_label) + + main_layout.add_widget(top_bar) + main_layout.add_widget(self.game) + return main_layout + + def restart_game(self, instance): + self.game.reset_game() + + +if __name__ == '__main__': + SnakeApp().run() diff --git a/snake game/snake_game_save.json b/snake game/snake_game_save.json new file mode 100644 index 0000000..1480e41 --- /dev/null +++ b/snake game/snake_game_save.json @@ -0,0 +1 @@ +{"high_score": 3} \ No newline at end of file diff --git a/stone-paper-scissors.py b/stone-paper-scissors.py new file mode 100644 index 0000000..ac04aab --- /dev/null +++ b/stone-paper-scissors.py @@ -0,0 +1,210 @@ +import tkinter as tk +import random +import json +import os +from datetime import datetime + +MOVES = {'R': 'Rock 🪨', 'P': 'Paper 📄', 'S': 'Scissors ✂️'} + +class RPSGame: + def __init__(self, root): + self.root = root + self.root.title("🎉 Rock Paper Scissors Ultimate 🎉") + self.root.geometry("500x600") + self.root.configure(bg="#FFFAE3") + + self.wins = 0 + self.losses = 0 + self.ties = 0 + self.streak = 0 + self.history = [] + self.load_data() + + self.build_ui() + + def build_ui(self): + tk.Label(self.root, text="ROCK PAPER SCISSORS!", font=("Comic Sans MS", 28, "bold"), + bg="#FFFAE3", fg="#FF6B6B").pack(pady=20) + + self.stats_label = tk.Label(self.root, font=("Comic Sans MS", 14), + bg="#FFFAE3", fg="#444") + self.stats_label.pack(pady=10) + + self.result_label = tk.Label(self.root, font=("Comic Sans MS", 20, "bold"), + width=30, height=2, bg="#FFFAE3") + self.result_label.pack(pady=20) + + btn_frame = tk.Frame(self.root, bg="#FFFAE3") + btn_frame.pack(pady=20) + + rock_btn = tk.Button(btn_frame, text="🪨 Rock", command=lambda: self.play('R')) + paper_btn = tk.Button(btn_frame, text="📄 Paper", command=lambda: self.play('P')) + scissors_btn = tk.Button(btn_frame, text="✂️ Scissors", command=lambda: self.play('S')) + + for btn in (rock_btn, paper_btn, scissors_btn): + btn.config(font=("Comic Sans MS", 14, "bold"), + bg="#FFD93D", fg="#444", + activebackground="#FF6B6B", activeforeground="#fff", + width=10, height=2, bd=4, relief="groove", cursor="hand2") + btn.pack(side="left", padx=15) + + tk.Button(self.root, text="📋 View History", command=self.show_history, + font=("Comic Sans MS", 12), bg="#5CD859", fg="white", + width=20, height=2).pack(pady=10) + + tk.Button(self.root, text="🏆 View Leaderboard", command=self.show_leaderboard, + font=("Comic Sans MS", 12), bg="#FF6B6B", fg="white", + width=20, height=2).pack(pady=10) + + tk.Button(self.root, text="🔄 Reset Stats", command=self.reset_game, + font=("Comic Sans MS", 12), bg="#FFD93D", fg="#444", + width=20, height=2).pack(pady=10) + + tk.Button(self.root, text="🚪 Quit", command=self.quit_game, + font=("Comic Sans MS", 12), bg="#FF6B6B", fg="white", + width=20, height=2).pack(pady=10) + + self.update_stats() + + def load_data(self): + if os.path.exists('rps_gui_save.json'): + try: + with open('rps_gui_save.json', 'r') as f: + data = json.load(f) + self.wins = data.get('wins', 0) + self.losses = data.get('losses', 0) + self.ties = data.get('ties', 0) + self.streak = data.get('streak', 0) + self.history = data.get('history', []) + except: + pass + + def save_data(self): + data = { + 'wins': self.wins, + 'losses': self.losses, + 'ties': self.ties, + 'streak': self.streak, + 'history': self.history + } + with open('rps_gui_save.json', 'w') as f: + json.dump(data, f) + + def adaptive_ai(self): + if len(self.history) < 3: + return random.choice(['R', 'P', 'S']) + last_moves = [h['player'] for h in self.history[-3:]] + most_common = max(set(last_moves), key=last_moves.count) + counter = {'R': 'P', 'P': 'S', 'S': 'R'} + return counter[most_common] + + def play(self, user_move): + computer_move = self.adaptive_ai() + result = self.determine_winner(user_move, computer_move) + + self.history.append({ + 'time': datetime.now().strftime("%Y-%m-%d %H:%M"), + 'player': user_move, + 'computer': computer_move, + 'result': result + }) + + feedback = { + 'win': ("You Win! 🥳", "#5CD859"), + 'loss': ("Computer Wins! 😝", "#FF6B6B"), + 'tie': ("It's a Draw! 🤝", "#FFD93D") + } + + text, color = feedback[result] + self.result_label.config(text=f"{MOVES[user_move]} vs {MOVES[computer_move]} — {text}", bg=color) + self.update_stats() + self.save_data() + + def determine_winner(self, player, computer): + if player == computer: + self.ties += 1 + self.streak = 0 + return 'tie' + elif (player == 'R' and computer == 'S') or \ + (player == 'P' and computer == 'R') or \ + (player == 'S' and computer == 'P'): + self.wins += 1 + self.streak += 1 + return 'win' + else: + self.losses += 1 + self.streak = 0 + return 'loss' + + def update_stats(self): + total = self.wins + self.losses + self.ties + win_rate = (self.wins / total * 100) if total > 0 else 0 + self.stats_label.config( + text=f"Wins: {self.wins} Losses: {self.losses} Ties: {self.ties}\n" + f"Streak: {self.streak} Win Rate: {win_rate:.1f}%" + ) + + def show_history(self): + history_window = tk.Toplevel(self.root) + history_window.title("📋 Game History") + history_window.configure(bg="#FFFAE3") + + tk.Label(history_window, text="Last 10 Matches", font=("Comic Sans MS", 16, "bold"), + bg="#FFFAE3").pack(pady=10) + + text = tk.Text(history_window, width=50, height=15, bg="#fff", font=("Comic Sans MS", 12)) + text.pack(padx=10, pady=10) + + for h in self.history[-10:]: + result = "Win" if h['result'] == 'win' else "Loss" if h['result'] == 'loss' else "Tie" + text.insert(tk.END, f"{h['time']}: You chose {MOVES[h['player']]}, Computer chose {MOVES[h['computer']]} — {result}\n") + + text.config(state='disabled') + + def show_leaderboard(self): + leaderboard_window = tk.Toplevel(self.root) + leaderboard_window.title("🏆 Leaderboard") + leaderboard_window.configure(bg="#FFFAE3") + + max_streak = 0 + current_streak = 0 + for h in self.history: + if h['result'] == 'win': + current_streak += 1 + max_streak = max(max_streak, current_streak) + else: + current_streak = 0 + + total_games = len(self.history) + win_rate = (self.wins / total_games * 100) if total_games > 0 else 0 + + tk.Label(leaderboard_window, text="Your Leaderboard", font=("Comic Sans MS", 16, "bold"), + bg="#FFFAE3").pack(pady=10) + + stats = f""" + Total Games: {total_games} + Wins: {self.wins} + Losses: {self.losses} + Ties: {self.ties} + Longest Win Streak: {max_streak} + Current Win Streak: {self.streak} + Win Rate: {win_rate:.1f}% + """ + tk.Label(leaderboard_window, text=stats.strip(), justify='left', + bg="#FFFAE3", font=("Comic Sans MS", 12)).pack(pady=20) + + def reset_game(self): + self.wins = self.losses = self.ties = self.streak = 0 + self.history = [] + self.update_stats() + self.result_label.config(text="", bg="#FFFAE3") + self.save_data() + + def quit_game(self): + self.save_data() + self.root.quit() + + +root = tk.Tk() +app = RPSGame(root) +root.mainloop() diff --git a/stone-paper-sssc.py b/stone-paper-sssc.py deleted file mode 100644 index 47c766f..0000000 --- a/stone-paper-sssc.py +++ /dev/null @@ -1,59 +0,0 @@ -import random, sys - -print("Let's Play ROCK PAPER SCISSORS GAME!") - -wins = 0 -losses = 0 -ties = 0 - -while True: - print("Current streak: %s Wins, %s Losses, %s Ties" % (wins, losses, ties)) - while True: - print("Type 'Q' to quit \n'R' for ROCK, 'P' for PAPER, 'S' for SCISSORS") - playermove = input().upper() - if playermove == "Q": - sys.exit() - if playermove == "R" or playermove == "P" or playermove == "S": - break - - if playermove == "R": - print("ROCK versus...") - if playermove == "P": - print("PAPER versus...") - if playermove == "S": - print("SCISSORS versus...") - - randomNum = random.randint(1, 3) - if randomNum == 1: - compMove = "R" - print("ROCK") - if randomNum == 2: - compMove = "P" - print("PAPER") - if randomNum == 3: - compMove = "S" - print("SCISSORS") - - if playermove == compMove: - print("It's a tie!") - ties += 1 - elif playermove == "R" and compMove == "P": - print("It's a loss!") - losses += 1 - elif playermove == "R" and compMove == "S": - print("It's a win!") - wins += 1 - elif playermove == "P" and compMove == "S": - print("It's a loss!") - losses += 1 - elif playermove == "P" and compMove == "R": - print("It's a win!") - wins += 1 - elif playermove == "S" and compMove == "R": - print("It's a loss!") - losses += 1 - elif playermove == "S" and compMove == "P": - print("It's a win!") - wins += 1 - else: - print("Thanks for trying my game")