-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A gama pong is created and connected to the event buffer. #102
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
import sys | ||
|
||
import pygame | ||
import socket | ||
import random | ||
from threading import Thread | ||
|
||
# initialize pygame | ||
pygame.init() | ||
|
||
# game canvas | ||
screen_width = 600 | ||
screen_height = 400 | ||
screen = pygame.display.set_mode((screen_width, screen_height)) | ||
|
||
# game variables | ||
ball_speed_x = random.choice([1, -1]) * random.randint(2, 4) | ||
ball_speed_y = -random.randint(2, 4) | ||
paddle_speed = 0 | ||
paddle_width = 100 | ||
paddle_height = 20 | ||
ball_radius = 10 | ||
|
||
# game score | ||
survive_time = 0 | ||
|
||
# game state | ||
ball_pos = [screen_width // 2, screen_height // 2] | ||
paddle_pos = [screen_width // 2 - paddle_width // 2, screen_height - paddle_height] | ||
|
||
# Socket | ||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
game_address = ("localhost", 12345) | ||
control_address = ("localhost", 54321) | ||
|
||
font = pygame.font.Font(None, 36) | ||
|
||
|
||
def reset_game(): | ||
global ball_pos, ball_speed_x, ball_speed_y, paddle_pos, paddle_speed | ||
ball_pos = [screen_width // 2, screen_height // 2] | ||
ball_speed_x = random.choice([1, -1]) * random.randint(2, 4) | ||
ball_speed_y = -random.randint(2, 4) | ||
paddle_pos = [screen_width // 2 - paddle_width // 2, screen_height - paddle_height] | ||
paddle_speed = 0 | ||
|
||
|
||
def send_status(message=None): | ||
|
||
if message is None: | ||
|
||
if ball_pos[0] < paddle_pos[0]: | ||
message = "<{left} --> [on]>." | ||
elif ball_pos[0] > paddle_pos[0] + paddle_width: | ||
message = "<{right} --> [on]>." | ||
else: | ||
message = "<{center} --> [on]>." | ||
|
||
sock.sendto(message.encode(), control_address) | ||
|
||
|
||
def game_loop(): | ||
global ball_pos, ball_speed_x, ball_speed_y, paddle_pos, paddle_speed, survive_time, survive_time_curve | ||
|
||
running = True | ||
clock = pygame.time.Clock() | ||
|
||
while running: | ||
|
||
survive_time += 1 | ||
|
||
for event in pygame.event.get(): | ||
if event.type == pygame.QUIT: | ||
running = False | ||
|
||
if not running: | ||
break | ||
|
||
# update ball pos | ||
ball_pos[0] += ball_speed_x | ||
ball_pos[1] += ball_speed_y | ||
|
||
# boundary collision check | ||
if ball_pos[0] <= ball_radius or ball_pos[0] >= screen_width - ball_radius: | ||
ball_speed_x = -ball_speed_x | ||
if ball_pos[1] <= ball_radius: | ||
ball_speed_y = -ball_speed_y | ||
|
||
# paddle collision check | ||
if ball_pos[1] + ball_radius >= paddle_pos[1] and paddle_pos[0] < ball_pos[0] < paddle_pos[0] + paddle_width: | ||
ball_speed_y = -ball_speed_y # toggle y-axis speed | ||
|
||
# optional | ||
# # increase difficulty, ball speed change on collusion | ||
# offset = (ball_pos[0] - (paddle_pos[0] + paddle_width / 2)) / (paddle_width / 2) | ||
# ball_speed_x += offset * 2 # 2 is a hyperparameter | ||
|
||
# game failed | ||
if ball_pos[1] >= screen_height: | ||
send_status("GAME FAILED") | ||
survive_time = 0 | ||
reset_game() # restart | ||
|
||
paddle_pos[0] += paddle_speed | ||
|
||
if paddle_pos[0] < 0: | ||
paddle_pos[0] = 0 | ||
if paddle_pos[0] > screen_width - paddle_width: | ||
paddle_pos[0] = screen_width - paddle_width | ||
|
||
screen.fill((0, 0, 0)) | ||
pygame.draw.rect(screen, (255, 255, 255), | ||
pygame.Rect(paddle_pos[0], paddle_pos[1], paddle_width, paddle_height)) | ||
pygame.draw.circle(screen, (255, 255, 255), ball_pos, ball_radius) | ||
# show the survived time | ||
score_text = font.render(f"Score: {survive_time}", True, (255, 255, 255)) | ||
screen.blit(score_text, (10, 10)) # in the top left corner | ||
pygame.display.flip() | ||
|
||
send_status() | ||
clock.tick(60) | ||
|
||
pygame.quit() | ||
sys.exit() | ||
|
||
|
||
def receive_commands(): | ||
global paddle_speed | ||
sock.bind(game_address) | ||
|
||
while True: | ||
data, _ = sock.recvfrom(1024) | ||
command = data.decode() | ||
print(f"command received: {command}") | ||
|
||
if command == "^left": | ||
paddle_speed = -5 | ||
elif command == "^right": | ||
paddle_speed = 5 | ||
elif command == "^stop": | ||
paddle_speed = 0 | ||
|
||
|
||
t = Thread(target=receive_commands) | ||
t.daemon = True | ||
t.start() | ||
|
||
game_loop() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import random | ||
from threading import Thread | ||
|
||
from pynars import Narsese | ||
from pynars.NARS import Reasoner | ||
from pynars.NARS.DataStructures import EventBuffer | ||
import socket | ||
|
||
from pynars.Narsese import Task | ||
|
||
# reasoner | ||
nars = Reasoner(1000, 1000) | ||
|
||
# Socket | ||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
control_address = ("localhost", 54321) | ||
game_address = ("localhost", 12345) | ||
|
||
# EB | ||
capacity = 5 | ||
event_buffer: EventBuffer = EventBuffer(capacity=capacity) | ||
|
||
# clock | ||
stamp = 0 | ||
|
||
|
||
def create_task_util(s): | ||
global stamp | ||
task: Task = Narsese.parser.parse(s) | ||
task.stamp.t_occurrence = stamp | ||
stamp += 1 | ||
return task | ||
|
||
|
||
def receive_status(): | ||
global event_buffer | ||
sock.bind(control_address) | ||
while True: | ||
data, _ = sock.recvfrom(1024) | ||
status = data.decode() | ||
print(f"game state received: {status}") | ||
if status != "GAME FAILED": | ||
event_buffer.put(create_task_util(status)) | ||
|
||
|
||
def send_commands(command: str): | ||
sock.sendto(command.encode(), game_address) | ||
|
||
|
||
if __name__ == "__main__": | ||
t = Thread(target=receive_status) | ||
t.start() | ||
|
||
# NARS cycle | ||
limit = 1000000 | ||
count = -1 | ||
|
||
while True: | ||
|
||
count += 1 | ||
|
||
if count > limit: | ||
break | ||
|
||
task_from_EB = event_buffer.generate_temporal_sentences() | ||
if len(task_from_EB) != 0: | ||
nars.input_narsese(str(task_from_EB[0])) | ||
|
||
tasks_derived, judgement_revised, goal_revised, answers_question, answers_quest, ( | ||
task_operation_return, task_executed) = nars.cycle() | ||
|
||
for each in tasks_derived: | ||
if each.is_goal and each.term in ["^left", "^right", "^stop"]: | ||
send_commands(each.term) | ||
|
||
# motor babbling | ||
if random.random() > 0.8 and count < 2000: # 20% chance | ||
opt = random.choice(["^left", "^right", "^stop"]) | ||
event_buffer.put(create_task_util(opt + ".")) | ||
send_commands(opt) | ||
print("!") |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -116,6 +116,6 @@ def put(self, event_task_to_insert: Task): | |
self.buffer.pop(0) | ||
|
||
def can_task_enter(self, task: Task): | ||
return task.is_event \ | ||
and task.term.type == TermType.STATEMENT \ | ||
return (task.is_event or task.is_operation) \ | ||
and (task.term.type == TermType.STATEMENT or task.term.type == TermType.ATOM) \ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree it is correct to let Operations in the Buffer. But, not the Atomic terms, since Atomic terms cannot be events. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But if there is an operation, say "^left", how to build relations between it and other events. I think in the design, this is achieved in the internal buffer, though currently we don't have it. Probably we can allow a special data structure containing only operations, running parallel with the event buffer, to create schemas like "event, operation / event". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The term The Operation itself,
How it works is, when NARS executes Operation "^left", it returns a "motor copy", allowing NARS to directly observe the Event, If this Event comes through the Narsese channel, in our current design, it should automatically flow into the EventBuffer and form the temporal inductions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a side note. I always found it a little weird that (*,self) is a valid syntax on OpenNARS. Products should have two or more components and in this case it always felt like a hack to me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You will have to take that one up with Professor. I do not have a strong opinion on the theory, but in the code I think we should stick to how the book defines it, unless we can find a better alternative |
||
and not task.term.is_higher_order |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to use EventBuffer in this file, there is already one in the Reasoner, which is executed at every working cycle.
All that is necessary for the Pong game is to input the Sensory Events into the Narsese Channel. The NARS should automatically process them with EventBuffer and put the results into the Overall Experience Buffer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The flow is like this currently:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, that sounds nice. To confirm, we just need to put all sensory events to the Narsese channel. Do you know the API of inputting in the Narsese channel?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
nars.input_narsese(string "")
should work