From 4409997ba7a29b365c4d70ba52a165ab0165bd68 Mon Sep 17 00:00:00 2001 From: sunandamishra0906 Date: Sun, 2 Aug 2026 10:36:58 +0530 Subject: [PATCH 1/6] integrated the keyboard gesture and trained model --- play.py | 143 +++++++++++++++++++++++++++++++++++++++++++++-- requirements.txt | 36 ++++++------ 2 files changed, 156 insertions(+), 23 deletions(-) diff --git a/play.py b/play.py index 64d7083..b91e4ab 100644 --- a/play.py +++ b/play.py @@ -1,3 +1,4 @@ +""" import argparse import ale_py @@ -17,9 +18,20 @@ def main(args): reward_sum = 0 # load model - model = torch.load(args.model_path, weights_only=False) + #model = torch.load(args.model_path, weights_only=False) + ##model.eval() + #model.to(args.device) + device = torch.device(args.device if torch.cuda.is_available() else "cpu") + print(f"Using device: {device}") + + model = torch.load( + args.model_path, + map_location=device, + weights_only=False + ) + model.eval() - model.to(args.device) + model.to(device) # init the game env = gym.make("Pong-v4", render_mode="rgb_array") # render_mode="human" option fails on my PC, thus used opencv @@ -28,8 +40,8 @@ def main(args): while True: # preprocess the observation, set input to network to be difference image - cur_x = image_preprocess(observation, device=args.device) - input_x = cur_x - prev_x if prev_x is not None else torch.zeros(D).to(args.device) + cur_x = image_preprocess(observation, device=device) + input_x = cur_x - prev_x if prev_x is not None else torch.zeros(D, device=device) prev_x = cur_x # model forward pass @@ -52,7 +64,7 @@ def parse_args(): ap = argparse.ArgumentParser('Evaluate Parser') ap.add_argument('--model_path', type=str, default='best_reward_model.pth', help="Path to the model .pth file") - ap.add_argument('--device', type=str, default='cuda:0', + ap.add_argument('--device', type=str, default='cuda' if torch.cuda.is_available() else 'cpu', help="Device to use") args = ap.parse_args() return args @@ -61,3 +73,124 @@ def parse_args(): if __name__ == '__main__': args = parse_args() main(args) +""" + +import argparse +import cv2 +import ale_py +import gymnasium as gym +import numpy as np +import torch + +from utils import display_observation, image_preprocess + +@torch.no_grad() +def main(args): + # init the parameters + display = True + D = 80 * 80 # input dimensionality: 80x80 grid + prev_x = None + reward_sum = 0 + + device = torch.device(args.device if torch.cuda.is_available() else "cpu") + print(f"Using device: {device}") + + # Load model only if evaluating the AI + if args.mode == "ai": + model = torch.load( + args.model_path, + map_location=device, + weights_only=False + ) + model.eval() + model.to(device) + else: + print("=====================================================") + print("HUMAN MODE SELECTED") + print("Control the right paddle using your KEYBOARD.") + print(" - Press 'W' to move UP") + print(" - Press 'S' to move DOWN") + print(" - Press 'Q' to QUIT") + print("Make sure the OpenCV game window is in focus!") + + # init the game + env = gym.make("Pong-v4", render_mode="rgb_array") + observation, info = env.reset(seed=42) + + # Initialize OpenCV window to capture keyboard input + cv2.namedWindow("Pong") + + while True: + if args.mode == "ai": + # preprocess the observation, set input to network to be difference image + cur_x = image_preprocess(observation, device=device) + input_x = cur_x - prev_x if prev_x is not None else torch.zeros(D, device=device) + prev_x = cur_x + + # model forward pass + output = model(input_x) + action = 2 if np.random.uniform() < output.item() else 3 # roll the dice! + + # Allow user to quit early + key = cv2.waitKey(1) & 0xFF + if key == ord('q'): + break + else: + # Take user input via KEYBOARD + # A 30ms wait provides a playable ~33 FPS for the human + key = cv2.waitKey(30) & 0xFF + + if key == ord('w'): + action = 2 # UP + elif key == ord('s'): + action = 3 # DOWN + elif key == ord('q'): + break # QUIT + else: + action = 0 # NOOP + + # step the environment and get new measurements + observation, reward, terminated, truncated, info = env.step(action) + reward_sum += reward + + # display game if needed + if display: + # Convert RGB array from Gymnasium to BGR for OpenCV + #bgr_image = cv2.cvtColor(observation, cv2.COLOR_RGB2BGR) + #cv2.imshow("Pong", bgr_image) + + bgr_image = cv2.cvtColor(observation, cv2.COLOR_RGB2BGR) + + # Scale by 4x + display_image = cv2.resize( + bgr_image, + None, + fx=4, + fy=4, + interpolation=cv2.INTER_NEAREST + ) + + cv2.imshow("Pong", display_image) + + if terminated or truncated: # an episode finished, someone reached 21 scores + print('Episode total reward:', reward_sum) + break + + cv2.destroyAllWindows() + env.close() + +def parse_args(): + ap = argparse.ArgumentParser('Evaluate Parser') + ap.add_argument('--model_path', type=str, default='best_reward_model.pth', + help="Path to the model .pth file") + ap.add_argument('--device', type=str, default='cuda' if torch.cuda.is_available() else 'cpu', + help="Device to use") + ap.add_argument('--mode', type=str, default='human', choices=['human', 'ai'], + help="Choose 'human' to play via keyboard against the Atari AI, or 'ai' to evaluate the model.") + args = ap.parse_args() + return args + + +if __name__ == '__main__': + args = parse_args() + main(args) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f53b8e9..b733d86 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ absl-py==2.1.0 -ale-py==0.10.2 -box2d-py==2.3.5 +ale-py==0.11.2 +# box2d-py==2.3.5 cffi==1.17.1 chex==0.1.88 cloudpickle==3.1.1 @@ -34,22 +34,22 @@ moviepy==2.1.2 mpmath==1.3.0 msgpack==1.1.0 mujoco==3.2.7 -mujoco-py==2.1.2.14 +# mujoco-py==2.1.2.14 nest-asyncio==1.6.0 networkx==3.4.2 -nvidia-cublas-cu12 -nvidia-cuda-cupti-cu12 -nvidia-cuda-nvrtc-cu12 -nvidia-cuda-runtime-cu12 -nvidia-cudnn-cu12 -nvidia-cufft-cu12 -nvidia-curand-cu12 -nvidia-cusolver-cu12 -nvidia-cusparse-cu12 -nvidia-cusparselt-cu12 -nvidia-nccl-cu12 -nvidia-nvjitlink-cu12 -nvidia-nvtx-cu12 +# nvidia-cublas-cu12 +# nvidia-cuda-cupti-cu12 +# nvidia-cuda-nvrtc-cu12 +# nvidia-cuda-runtime-cu12 +# nvidia-cudnn-cu12 +# nvidia-cufft-cu12 +# nvidia-curand-cu12 +# nvidia-cusolver-cu12 +# nvidia-cusparse-cu12 +# nvidia-cusparselt-cu12 +# nvidia-nccl-cu12 +# nvidia-nvjitlink-cu12 +# nvidia-nvtx-cu12 opencv-python==4.10.0.84 opt_einsum==3.4.0 optax==0.2.4 @@ -77,6 +77,6 @@ toolz==1.0.0 torch==2.6.0 tqdm==4.67.1 treescope==0.1.9 -triton==3.2.0 +# triton==3.2.0 typing_extensions==4.12.2 -zipp==3.21.0 +zipp==3.21.0 \ No newline at end of file From d406bcc51a5e46e108e19d0c4a472c3eaf59e3c7 Mon Sep 17 00:00:00 2001 From: Rudra Patankar Date: Sun, 2 Aug 2026 11:10:17 +0530 Subject: [PATCH 2/6] Modified the arcade background to table tennis in play.py --- play.py | 285 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 234 insertions(+), 51 deletions(-) diff --git a/play.py b/play.py index b91e4ab..2c15225 100644 --- a/play.py +++ b/play.py @@ -75,15 +75,176 @@ def parse_args(): main(args) """ +# import argparse +# import cv2 +# import ale_py +# import gymnasium as gym +# import numpy as np +# import torch + +# from utils import display_observation, image_preprocess + +# @torch.no_grad() +# def main(args): +# # init the parameters +# display = True +# D = 80 * 80 # input dimensionality: 80x80 grid +# prev_x = None +# reward_sum = 0 + +# device = torch.device(args.device if torch.cuda.is_available() else "cpu") +# print(f"Using device: {device}") + +# # Load model only if evaluating the AI +# if args.mode == "ai": +# model = torch.load( +# args.model_path, +# map_location=device, +# weights_only=False +# ) +# model.eval() +# model.to(device) +# else: +# print("=====================================================") +# print("HUMAN MODE SELECTED") +# print("Control the right paddle using your KEYBOARD.") +# print(" - Press 'W' to move UP") +# print(" - Press 'S' to move DOWN") +# print(" - Press 'Q' to QUIT") +# print("Make sure the OpenCV game window is in focus!") + +# # init the game +# env = gym.make("Pong-v4", render_mode="rgb_array") +# observation, info = env.reset(seed=42) + +# # Initialize OpenCV window to capture keyboard input +# cv2.namedWindow("Pong") + +# while True: +# if args.mode == "ai": +# # preprocess the observation, set input to network to be difference image +# cur_x = image_preprocess(observation, device=device) +# input_x = cur_x - prev_x if prev_x is not None else torch.zeros(D, device=device) +# prev_x = cur_x + +# # model forward pass +# output = model(input_x) +# action = 2 if np.random.uniform() < output.item() else 3 # roll the dice! + +# # Allow user to quit early +# key = cv2.waitKey(1) & 0xFF +# if key == ord('q'): +# break +# else: +# # Take user input via KEYBOARD +# # A 30ms wait provides a playable ~33 FPS for the human +# key = cv2.waitKey(30) & 0xFF + +# if key == ord('w'): +# action = 2 # UP +# elif key == ord('s'): +# action = 3 # DOWN +# elif key == ord('q'): +# break # QUIT +# else: +# action = 0 # NOOP + +# # step the environment and get new measurements +# observation, reward, terminated, truncated, info = env.step(action) +# reward_sum += reward + +# # display game if needed +# if display: +# # Convert RGB array from Gymnasium to BGR for OpenCV +# #bgr_image = cv2.cvtColor(observation, cv2.COLOR_RGB2BGR) +# #cv2.imshow("Pong", bgr_image) + +# bgr_image = cv2.cvtColor(observation, cv2.COLOR_RGB2BGR) + +# # Scale by 4x +# display_image = cv2.resize( +# bgr_image, +# None, +# fx=4, +# fy=4, +# interpolation=cv2.INTER_NEAREST +# ) + +# cv2.imshow("Pong", display_image) + +# if terminated or truncated: # an episode finished, someone reached 21 scores +# print('Episode total reward:', reward_sum) +# break + +# cv2.destroyAllWindows() +# env.close() + +# def parse_args(): +# ap = argparse.ArgumentParser('Evaluate Parser') +# ap.add_argument('--model_path', type=str, default='best_reward_model.pth', +# help="Path to the model .pth file") +# ap.add_argument('--device', type=str, default='cuda' if torch.cuda.is_available() else 'cpu', +# help="Device to use") +# ap.add_argument('--mode', type=str, default='human', choices=['human', 'ai'], +# help="Choose 'human' to play via keyboard against the Atari AI, or 'ai' to evaluate the model.") +# args = ap.parse_args() +# return args + + +# if __name__ == '__main__': +# args = parse_args() +# main(args) + import argparse import cv2 import ale_py import gymnasium as gym import numpy as np import torch +import time from utils import display_observation, image_preprocess +def draw_tennis_racket(img, cx, cy, is_left_player, scale): + """Draws a tennis racket at the specified center coordinates.""" + # Racket dimensions based on scale + handle_width = 2 * scale + handle_length = 8 * scale + head_width = 6 * scale + head_height = 9 * scale + + # Colors (BGR for OpenCV) + handle_color = (20, 60, 100) # Brownish handle + frame_color = (180, 180, 180) # Silver frame + string_color = (50, 50, 50) # Dark string grid + + if is_left_player: + # Handle pointing left + cv2.rectangle(img, (cx - head_width//2 - handle_length, cy - handle_width//2), + (cx - head_width//2, cy + handle_width//2), handle_color, -1) + else: + # Handle pointing right + cv2.rectangle(img, (cx + head_width//2, cy - handle_width//2), + (cx + head_width//2 + handle_length, cy + handle_width//2), handle_color, -1) + + # Draw strings (grid) + for i in range(-head_width//2 + 2, head_width//2, 3): + cv2.line(img, (cx + i, cy - head_height//2 + 2), (cx + i, cy + head_height//2 - 2), string_color, 1) + for i in range(-head_height//2 + 2, head_height//2, 3): + cv2.line(img, (cx - head_width//2 + 2, cy + i), (cx + head_width//2 - 2, cy + i), string_color, 1) + + # Draw racket head frame + cv2.ellipse(img, (cx, cy), (head_width//2, head_height//2), 0, 0, 360, frame_color, 2, cv2.LINE_AA) + +def draw_tennis_ball(img, cx, cy, scale): + """Draws a yellow tennis ball with white seams.""" + radius = 2 * scale + # Draw yellow ball + cv2.circle(img, (cx, cy), radius, (0, 220, 220), -1, cv2.LINE_AA) + # Draw white seams (approximated with arcs) + cv2.ellipse(img, (cx - radius//2, cy), (radius//2, int(radius*0.8)), 0, -60, 60, (255, 255, 255), 1, cv2.LINE_AA) + cv2.ellipse(img, (cx + radius//2, cy), (radius//2, int(radius*0.8)), 0, 120, 240, (255, 255, 255), 1, cv2.LINE_AA) + @torch.no_grad() def main(args): # init the parameters @@ -91,6 +252,7 @@ def main(args): D = 80 * 80 # input dimensionality: 80x80 grid prev_x = None reward_sum = 0 + duration_seconds = 300 # 5 minutes device = torch.device(args.device if torch.cuda.is_available() else "cpu") print(f"Using device: {device}") @@ -106,91 +268,112 @@ def main(args): model.to(device) else: print("=====================================================") - print("HUMAN MODE SELECTED") + print("🕹️ HUMAN MODE SELECTED") print("Control the right paddle using your KEYBOARD.") print(" - Press 'W' to move UP") print(" - Press 'S' to move DOWN") print(" - Press 'Q' to QUIT") + print(f"Game will run continuously for {duration_seconds // 60} minutes.") print("Make sure the OpenCV game window is in focus!") + print("=====================================================") # init the game env = gym.make("Pong-v4", render_mode="rgb_array") observation, info = env.reset(seed=42) - # Initialize OpenCV window to capture keyboard input - cv2.namedWindow("Pong") + cv2.namedWindow("Pong Table Tennis Pro") + + start_time = time.time() - while True: + while time.time() - start_time < duration_seconds: if args.mode == "ai": - # preprocess the observation, set input to network to be difference image cur_x = image_preprocess(observation, device=device) input_x = cur_x - prev_x if prev_x is not None else torch.zeros(D, device=device) prev_x = cur_x - # model forward pass output = model(input_x) - action = 2 if np.random.uniform() < output.item() else 3 # roll the dice! + action = 2 if np.random.uniform() < output.item() else 3 - # Allow user to quit early key = cv2.waitKey(1) & 0xFF if key == ord('q'): break else: - # Take user input via KEYBOARD - # A 30ms wait provides a playable ~33 FPS for the human key = cv2.waitKey(30) & 0xFF - - if key == ord('w'): - action = 2 # UP - elif key == ord('s'): - action = 3 # DOWN - elif key == ord('q'): - break # QUIT - else: - action = 0 # NOOP + if key == ord('w'): action = 2 + elif key == ord('s'): action = 3 + elif key == ord('q'): break + else: action = 0 - # step the environment and get new measurements observation, reward, terminated, truncated, info = env.step(action) reward_sum += reward - # display game if needed if display: - # Convert RGB array from Gymnasium to BGR for OpenCV - #bgr_image = cv2.cvtColor(observation, cv2.COLOR_RGB2BGR) - #cv2.imshow("Pong", bgr_image) - bgr_image = cv2.cvtColor(observation, cv2.COLOR_RGB2BGR) - - # Scale by 4x - display_image = cv2.resize( - bgr_image, - None, - fx=4, - fy=4, - interpolation=cv2.INTER_NEAREST - ) - - cv2.imshow("Pong", display_image) - - if terminated or truncated: # an episode finished, someone reached 21 scores - print('Episode total reward:', reward_sum) - break - + scale = 4 + h, w = bgr_image.shape[:2] + + # 1. Create a blank canvas scaled up by 4x + custom_frame = np.zeros((h * scale, w * scale, 3), dtype=np.uint8) + + # 2. Preserve the original score area at the top + score_area = cv2.resize(bgr_image[0:34, :], (w * scale, 34 * scale), interpolation=cv2.INTER_NEAREST) + custom_frame[0:34*scale, :] = score_area + + # 3. Draw the Green Table Tennis Court background + cv2.rectangle(custom_frame, (0, 34*scale), (w*scale, h*scale), (60, 140, 60), -1) + cv2.rectangle(custom_frame, (10*scale, 40*scale), (w*scale - 10*scale, h*scale - 5*scale), (255, 255, 255), max(1, scale//2)) + cv2.line(custom_frame, (w*scale//2, 40*scale), (w*scale//2, h*scale - 5*scale), (200, 200, 200), max(1, scale//2)) + + # 4. Locate the objects using color masking + play_area = bgr_image[34:, :] + + # Detect Left Paddle + left_mask = cv2.inRange(play_area, np.array([50, 100, 180]), np.array([100, 160, 240])) + contours, _ = cv2.findContours(left_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + for cnt in contours: + lx, ly, lw, lh = cv2.boundingRect(cnt) + if lh > 5: # Valid paddle + draw_tennis_racket(custom_frame, (lx + lw//2) * scale, (ly + 34 + lh//2) * scale, is_left_player=True, scale=scale) + + # Detect Right Paddle + right_mask = cv2.inRange(play_area, np.array([70, 150, 70]), np.array([120, 210, 120])) + contours, _ = cv2.findContours(right_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + for cnt in contours: + rx, ry, rw, rh = cv2.boundingRect(cnt) + if rh > 5: # Valid paddle + draw_tennis_racket(custom_frame, (rx + rw//2) * scale, (ry + 34 + rh//2) * scale, is_left_player=False, scale=scale) + + # Detect Ball using Contours instead of flat bounding box + ball_mask = cv2.inRange(play_area, np.array([200, 200, 200]), np.array([255, 255, 255])) + contours, _ = cv2.findContours(ball_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + for cnt in contours: + bx, by, bw, bh = cv2.boundingRect(cnt) + # Ignore the center net pixels + if not (78 <= bx <= 82): + if bw < 5 and bh < 5: # Ball is tiny + draw_tennis_ball(custom_frame, (bx + bw//2) * scale, (by + 34 + bh//2) * scale, scale=scale) + + cv2.imshow("Pong Table Tennis Pro", custom_frame) + + # Reset instead of breaking when the game ends + if terminated or truncated: + print(f"Match concluded! Reward sum: {reward_sum}. Resetting for next match...") + observation, info = env.reset() + prev_x = None # Crucial to reset AI motion frame + reward_sum = 0 + + print("5 minutes elapsed. Exiting game.") cv2.destroyAllWindows() env.close() def parse_args(): ap = argparse.ArgumentParser('Evaluate Parser') - ap.add_argument('--model_path', type=str, default='best_reward_model.pth', - help="Path to the model .pth file") - ap.add_argument('--device', type=str, default='cuda' if torch.cuda.is_available() else 'cpu', - help="Device to use") - ap.add_argument('--mode', type=str, default='human', choices=['human', 'ai'], - help="Choose 'human' to play via keyboard against the Atari AI, or 'ai' to evaluate the model.") - args = ap.parse_args() - return args - + ap.add_argument('--model_path', type=str, default='best_reward_model.pth', help="Path to the model") + ap.add_argument('--device', type=str, default='cuda' if torch.cuda.is_available() else 'cpu') + ap.add_argument('--mode', type=str, default='human', choices=['human', 'ai']) + return ap.parse_args() if __name__ == '__main__': args = parse_args() - main(args) \ No newline at end of file + main(args) From 87bb90ace4c142e828144574a7d224de5617bdc9 Mon Sep 17 00:00:00 2001 From: Rudra Patankar Date: Sun, 2 Aug 2026 11:14:21 +0530 Subject: [PATCH 3/6] Screenshot of tennis arcade --- images/Screenshot 2026-08-02 111341.png | Bin 0 -> 20797 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/Screenshot 2026-08-02 111341.png diff --git a/images/Screenshot 2026-08-02 111341.png b/images/Screenshot 2026-08-02 111341.png new file mode 100644 index 0000000000000000000000000000000000000000..b0ddbb09e6a1dc3516ae4383c9b4d5c17641af2a GIT binary patch literal 20797 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 I Date: Sun, 2 Aug 2026 11:14:59 +0530 Subject: [PATCH 4/6] Changed the execution picture --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1194cdf..539ad79 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The RL agent learns to play Pong via trial and error from pixels, using Policy G The training code is located at `main.py`, and the slightly modified original Andrej's code is stored at `karpathys_code.py` (it was adapted for Python 3.10). -![My Image](images/screenshot.png) +![My Image](images/Screenshot 2026-08-02 111341.png) # Installation From 214545765f669cc551a6b539fb211fda8af0e068 Mon Sep 17 00:00:00 2001 From: Rudra Patankar Date: Sun, 2 Aug 2026 11:15:45 +0530 Subject: [PATCH 5/6] Rename Screenshot 2026-08-02 111341.png to Screenshot_1.png --- ...nshot 2026-08-02 111341.png => Screenshot_1.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename images/{Screenshot 2026-08-02 111341.png => Screenshot_1.png} (100%) diff --git a/images/Screenshot 2026-08-02 111341.png b/images/Screenshot_1.png similarity index 100% rename from images/Screenshot 2026-08-02 111341.png rename to images/Screenshot_1.png From d8fc9e1770594b69f638641847a810ab33e72cb6 Mon Sep 17 00:00:00 2001 From: Rudra Patankar Date: Sun, 2 Aug 2026 11:16:12 +0530 Subject: [PATCH 6/6] Updated working screenshot --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 539ad79..986ca77 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The RL agent learns to play Pong via trial and error from pixels, using Policy G The training code is located at `main.py`, and the slightly modified original Andrej's code is stored at `karpathys_code.py` (it was adapted for Python 3.10). -![My Image](images/Screenshot 2026-08-02 111341.png) +![My Image](images/Screenshot_1.png) # Installation