Skip to content
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

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions pong/pong_game.py
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()
81 changes: 81 additions & 0 deletions pong/pong_server.py
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]))
Copy link
Collaborator

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.

Copy link
Collaborator

@ccrock4t ccrock4t Mar 15, 2024

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:

image

Copy link
Collaborator Author

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?

Copy link
Collaborator

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


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("!")
4 changes: 2 additions & 2 deletions pynars/NARS/DataStructures/_py/Buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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".

Copy link
Collaborator

@ccrock4t ccrock4t Mar 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The term left is Atomic, though it is actually the name of the "Operator", not the Operation itself.
(The NAL book refers to this term as the "Operator" (p.146))

The Operation itself, ^left, is shorthand for a first-order Event that uses Product and SELF term together with the "Operator" relating them, for example:

^left $\Leftrightarrow \langle (*, self) \rightarrow left \rangle$


How it works is, when NARS executes Operation "^left", it returns a "motor copy", allowing NARS to directly observe the Event, $\langle(*, self) \rightarrow left\rangle. :|:$

If this Event comes through the Narsese channel, in our current design, it should automatically flow into the EventBuffer and form the temporal inductions.
Thus we will get statements like (A &/ ^left $\Rightarrow$ C) just from observing the motor copy

Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Collaborator

@ccrock4t ccrock4t Mar 15, 2024

Choose a reason for hiding this comment

The 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
Loading