From 90b0b2e3c11bae0d38f2715e331030e17dcc75c0 Mon Sep 17 00:00:00 2001 From: tia-chen Date: Wed, 6 Mar 2024 12:18:46 -0500 Subject: [PATCH 1/4] Added socket agent that takes keyboard input added keyboard_agent.py and modified socket message receiving logic in socket_env.py to receive more universal commands rather than only player commands --- keyboard_agent.py | 83 +++++++++++++++++++++++++++++++++++++++++++++++ socket_env.py | 25 +++++++++++--- 2 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 keyboard_agent.py diff --git a/keyboard_agent.py b/keyboard_agent.py new file mode 100644 index 0000000..5c737c4 --- /dev/null +++ b/keyboard_agent.py @@ -0,0 +1,83 @@ +# Author: Tia Chen +# Email: qingyan.chen@tufts.edu + +import argparse +import json +import socket +import pygame + +from utils import recv_socket_data + +def send_command(command): + print("Sending action: ", command) + sock_game.send(str.encode(command)) # send action to env + + output = recv_socket_data(sock_game) # get observation from env (wait for response before sending next command) + # output = json.loads(output) # Put back if you want to inspect received message + + # print("JSON: ", output) + +if __name__ == "__main__": + + parser = argparse.ArgumentParser() + + parser.add_argument( + 'player_id', + type=int, + help="Please provide the player id that you are playing for", + ) + args = parser.parse_args() + + action_commands = ['NOP', 'NORTH', 'SOUTH', 'EAST', 'WEST', 'TOGGLE_CART', 'INTERACT', 'CANCEL'] + + game_commands = ['ESCAPE', 'SAVE', 'TOGGLE_RECORD', 'PAUSE', 'REVERT'] + + print("action_commands: ", action_commands) + print("game_commands: ", game_commands) + + # Connect to Supermarket + HOST = '127.0.0.1' + PORT = 9000 + sock_game = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock_game.connect((HOST, PORT)) + + pygame.init() + + running = True + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.KEYDOWN: + # game events + if event.key == pygame.K_ESCAPE: + send_command('ESCAPE') + elif event.key == pygame.K_s: + send_command('SAVE') + elif event.key == pygame.K_r: + send_command('TOGGLE_RECORD') + elif event.key == pygame.K_p: + send_command('PAUSE') + elif event.key == pygame.K_z: + send_command('REVERT') + elif event.key == pygame.K_c: + send_command(str(args.player_id) + ' TOGGLE_CART') + elif event.key == pygame.K_RETURN: + send_command(str(args.player_id) + ' INTERACT') + elif event.key == pygame.K_b: + send_command(str(args.player_id) + ' CANCEL') + + + # Player events to send repeatedly to smooth transition + keys = pygame.key.get_pressed() + if keys[pygame.K_UP]: + send_command(str(args.player_id) + ' NORTH') + elif keys[pygame.K_DOWN]: + send_command(str(args.player_id) + ' SOUTH') + elif keys[pygame.K_LEFT]: + send_command(str(args.player_id) + ' WEST') + elif keys[pygame.K_RIGHT]: + send_command(str(args.player_id) + ' EAST') + + pygame.quit() + exit() \ No newline at end of file diff --git a/socket_env.py b/socket_env.py index cec2bdf..b6aa815 100755 --- a/socket_env.py +++ b/socket_env.py @@ -2,7 +2,6 @@ import argparse import datetime import json -from pprint import pprint import selectors import socket import types @@ -15,6 +14,7 @@ ACTION_COMMANDS = ['NOP', 'NORTH', 'SOUTH', 'EAST', 'WEST', 'INTERACT', 'TOGGLE_CART', 'CANCEL', 'SELECT'] +GAME_COMMANDS = ['ESCAPE', 'SAVE', 'TOGGLE_RECORD', 'PAUSE', 'REVERT'] class SupermarketEventHandler: def __init__(self, env, keyboard_input=False): @@ -98,8 +98,6 @@ def pause_game(self): elif event.type == pygame.KEYDOWN and event.key == pygame.K_p: waiting = False elif event.type == pygame.KEYDOWN and event.key == pygame.K_z: - # TIA NOTES: If we want to implement a revert function in - # keyboard input, we can slightly modify this to accomodate self.reverse() def handle_exploratory_events(self): @@ -229,6 +227,20 @@ def get_action_json(action, env_, obs, reward, done, info_=None): # action_json = {"hello": "world"} return action_json +def is_game_command(command_): + return command_ in GAME_COMMANDS + +def get_event(command_): + if command_ == 'ESCAPE': + return pygame.event.Event(pygame.KEYDOWN, key=pygame.K_ESCAPE) + elif command_ == 'SAVE': + return pygame.event.Event(pygame.KEYDOWN, key=pygame.K_s) + elif command_ == 'TOGGLE_RECORD': + return pygame.event.Event(pygame.KEYDOWN, key=pygame.K_r) + elif command_ == 'PAUSE': + return pygame.event.Event(pygame.KEYDOWN, key=pygame.K_p) + elif command_ == 'REVERT': + return pygame.event.Event(pygame.KEYDOWN, key=pygame.K_z) def is_single_player(command_): return ',' not in command_ @@ -237,7 +249,9 @@ def is_playback_mode(command_): return command == "Playback" def get_player_and_command(command_): + print("command_: ", command_) split_command = command_.split(' ') + print("split_command: ", split_command) if len(split_command) == 1: return 0, split_command[0], 0 elif len(split_command) == 2: @@ -417,7 +431,7 @@ def accept_wrapper(sock): curr_action = [(0,0)] * env.unwrapped.num_players e = [] if not args.headless: - handler.handle_events() # TIA TODO: INJECTION OF KEYBOARD COMMANDS + handler.handle_events() env.render() for key, mask in events: if key.data is None: @@ -442,6 +456,9 @@ def accept_wrapper(sock): data.outb = str.encode(json.dumps(json_to_send) + "\n") if is_playback_mode(command): env.unwrapped.game.is_playback = True + if is_game_command(command): + keydown_event = get_event(command) + pygame.event.post(keydown_event) if is_single_player(command): player, command, arg = get_player_and_command(command) e.append((key, mask, command)) From fda04140a5344c5533de37ca4aaebb5275c0f4b5 Mon Sep 17 00:00:00 2001 From: tia-chen Date: Wed, 6 Mar 2024 12:20:40 -0500 Subject: [PATCH 2/4] minor style fixes --- socket_env.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/socket_env.py b/socket_env.py index b6aa815..67f543b 100755 --- a/socket_env.py +++ b/socket_env.py @@ -249,9 +249,7 @@ def is_playback_mode(command_): return command == "Playback" def get_player_and_command(command_): - print("command_: ", command_) split_command = command_.split(' ') - print("split_command: ", split_command) if len(split_command) == 1: return 0, split_command[0], 0 elif len(split_command) == 2: From b6ec8fdaeada0ad909a2bb49c5bc1ce691ce58ae Mon Sep 17 00:00:00 2001 From: tia-chen Date: Wed, 6 Mar 2024 12:32:22 -0500 Subject: [PATCH 3/4] Added display to located control medium for sending keyboard command --- keyboard_agent.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/keyboard_agent.py b/keyboard_agent.py index 5c737c4..ef846b5 100644 --- a/keyboard_agent.py +++ b/keyboard_agent.py @@ -43,6 +43,11 @@ def send_command(command): pygame.init() + # Set up a display to accept keyboard command + screen_size = (400, 100) + screen = pygame.display.set_mode(screen_size) + pygame.display.set_caption("Control Display") + running = True while running: for event in pygame.event.get(): From 24a08cf79996bf44f3666e0110d7e2a8d795d7f4 Mon Sep 17 00:00:00 2001 From: Tia Chen <108846164+Tia-Chen@users.noreply.github.com> Date: Mon, 25 Mar 2024 13:40:59 -0400 Subject: [PATCH 4/4] Update README.md --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index d6ebd3d..38cd858 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,16 @@ You can then run it (assuming the simulation is already running in another termi ``` python socket_agent.py ``` + +### Running the simulation with Python agents that takes keyboard inputs + +Take a look at the "keyboard_agent.py" file which has an example of how to send keyboard input commands through a socket. + +``` + keyboard_agent.py +``` +A control display will pop up. Click on the display to focus on it and send keyboard commands. The program will convert the commands and send the action commands to the socket environment to carry out the game play. + ### Running the simulation with recorded action history Take a look at the "replay_agent_actions.py" file which has an example of how to send recorded commands from a file through a socket.