full refactor and upgrade code to first part of project

This commit is contained in:
Vadzim Valchkovich 2023-05-05 02:56:22 +02:00
parent ebc5cd61bd
commit 71fcd755aa
27 changed files with 423 additions and 219 deletions

View File

@ -17,4 +17,26 @@
## RUN INSTRUCTIONS ## RUN INSTRUCTIONS
pipenv run python main.py pipenv run python agent.py
## TODO
---
- [x] **Planowanie ruchu: Wymagania dot. pierwszego przyrostu**
- [x] Agent powinien dysponować co najmniej następującymi akcjami: ruch do przodu, obrót w lewo, obrót w prawo
- [x] Należy wykorzystać „Schemat procedury przeszukiwania grafu stanów“.
- [x] Należy zaimplementować strategię Breadth-First Search.
---
- [ ] Planowanie ruchu: Wymagania dot. drugiego przyrostu
- [ ] Należy wykorzystać „Schemat procedury przeszukiwania grafu stanów z uwzględnieniem kosztu“
- [ ] Należy zaimplementować strategię A\*, czyli zdefiniować funkcję wyznaczającą priorytet następników uwzględniającą zarówno koszt jak i odpowiednią heurystykę.
- [x] Agent powinien dysponować co najmniej następującymi akcjami: ruch do przodu, obrót w lewo, obrót w prawo.
- [ ] Koszt wjazdu na pola poszczególnych typów powinien być zróżnicowany.
> _Przykład: Koszt wjazdu traktora na pole marchewek to 10 a koszt wjazdu na pole puste to 1._
---

27
agent.py Normal file
View File

@ -0,0 +1,27 @@
from src.Engine import Engine
from src.obj.Waiter import Waiter
from src.obj.Block import Block
from src.obj.Kitchen import Kitchen
from src.obj.Table import Table
from src.UserController import UserController
from src.StateController import StateController
waiter = Waiter([0, 0], 0, 50, 450//50)
objects = [
Kitchen([0, 0], 0, 50, 450//50),
Table([3, 6], 0, 50, 450//50),
Table([2, 4], 0, 50, 450//50),
Table([1, 5], 0, 50, 450//50),
Block([3, 5], 0, 50, 450//50),
Block([1, 4], 0, 50, 450//50),
Block([2, 5], 0, 50, 450//50)
]
user = UserController(waiter)
state = StateController(waiter)
engine = Engine((450, 450), 50, user, state)
for o in objects:
engine.subscribe(o)
engine.loop()

76
main.py
View File

@ -1,76 +0,0 @@
import pygame
import random
from models.Kitchen import Kitchen
from models.Table import Table
from models.Waiter import Waiter
pygame.init()
screen_size = (600, 600)
screen = pygame.display.set_mode(screen_size)
square_size = 50
num_squares = screen_size[0] // square_size
squares = []
for i in range(num_squares):
row = []
for j in range(num_squares):
square_rect = pygame.Rect(
j * square_size, i * square_size, square_size, square_size)
row.append(square_rect)
squares.append(row)
tables = [
Table(square_size, screen_size, 2, 4),
Table(square_size, screen_size, 6, 5),
Table(square_size, screen_size, 4, 2),
Table(square_size, screen_size, 5, 6),
Table(square_size, screen_size, 4, 4),
]
kitchen = Kitchen(square_size, screen_size, 0, 0)
waiter = Waiter(square_size, screen_size, 0, 0)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
waiter.up()
elif event.key == pygame.K_DOWN:
waiter.down()
elif event.key == pygame.K_LEFT:
waiter.left()
elif event.key == pygame.K_RIGHT:
waiter.right()
elif event.key == pygame.K_ESCAPE:
for table in tables:
table.new_order()
screen.fill((255, 255, 255))
for row in squares:
for square_rect in row:
pygame.draw.rect(screen, (0, 0, 0), square_rect, 1)
for table in tables:
table.blit(screen)
kitchen.blit(screen)
waiter.blit(screen)
for table in tables:
if table.collision(waiter):
waiter.do_smth(table)
if kitchen.collision(waiter):
kitchen.take_orders(waiter)
pygame.display.flip()

View File

@ -1,18 +0,0 @@
import pygame
from models.Object import Object
from models.Waiter import Waiter
class Kitchen(Object):
def __init__(self, square_size, screen_size, left_square, top_square):
super().__init__(
'kitchen',
square_size,
screen_size,
left_square,
top_square
)
def take_orders(self, waiter: Waiter):
for table in waiter.get_order_list():
table.done_order()

View File

@ -1,39 +0,0 @@
import pygame
class Object:
def __init__(self, role, square_size, screen_size, left_square, top_square):
self.role = role
self.image = pygame.transform.scale(pygame.image.load(
'images/{0}.png'.format(role)), (square_size, square_size))
self.square_size = square_size
self.screen_size = screen_size
left = left_square * square_size
top = top_square * square_size
self.rect = pygame.Rect(left, top, square_size, square_size)
def up(self):
if self.rect.top > 0:
self.rect.top -= self.square_size
def down(self):
if self.rect.bottom < self.screen_size[1]:
self.rect.top += self.square_size
def left(self):
if self.rect.left > 0:
self.rect.left -= self.square_size
def right(self):
if self.rect.right < self.screen_size[0]:
self.rect.left += self.square_size
def blit(self, screen):
screen.blit(self.image, self.rect)
def collision(self, obj):
x = self.rect.left == obj.rect.left
y = self.rect.top == obj.rect.top
return x and y

View File

@ -1,41 +0,0 @@
import pygame
from models.Object import Object
class Table(Object):
def __init__(self, square_size, screen_size, left_square, top_square):
super().__init__(
'table',
square_size,
screen_size,
left_square,
top_square
)
self.state = 'table'
def new_order(self):
self.state = 'order'
self.update_pic()
def wait_order(self):
self.state = 'wait'
self.update_pic()
def done_order(self):
self.state = 'done'
self.update_pic()
def reset_order(self):
self.state = 'table'
self.update_pic()
def is_order(self) -> bool:
return self.state == 'order'
def is_done(self) -> bool:
return self.state == 'done'
def update_pic(self):
self.image = pygame.transform.scale(pygame.image.load(
'images/{0}.png'.format(self.state)), (self.square_size, self.square_size))

View File

@ -1,44 +0,0 @@
import pygame
from models.Object import Object
from models.Table import Table
class Waiter(Object):
def __init__(self, square_size, screen_size, left_square, top_square):
super().__init__(
'waiter',
square_size,
screen_size,
left_square,
top_square
)
self.orders_limit = 3
self.orders_list = []
def do_smth(self, table: Table):
if table.is_order():
self.take_order(table)
elif table.is_done():
self.deliver_order(table)
def take_order(self, table: Table):
if self.orders_limit <= 0:
return
if not table.is_order():
return
self.orders_limit -= 1
self.orders_list.append(table)
table.wait_order()
def deliver_order(self, table: Table):
if table.is_done() and table in self.orders_list:
self.orders_limit += 1
self.orders_list.remove(table)
table.reset_order()
def get_order_list(self) -> list[Table]:
return self.orders_list

80
src/Engine.py Normal file
View File

@ -0,0 +1,80 @@
import pygame
from .obj.Object import Object
from .UserController import UserController
from .StateController import StateController
class Engine:
def __init__(self, screen_size, square_size, user: UserController, state: StateController):
self.user = user
self.state = state
self.screen_size = screen_size
self.screen = pygame.display.set_mode(self.screen_size)
self.square_size = square_size
self.num_squares = self.screen_size[0] // self.square_size
self.squares = self.__init_squares_field__(
self.num_squares, self.square_size)
self.objects: list[Object] = []
self.runnin = False
def __init_squares_field__(self, num_squares, square_size):
squares = []
for i in range(num_squares):
row = []
for j in range(num_squares):
square_rect = pygame.Rect(
j * square_size, i * square_size,
square_size, square_size)
row.append(square_rect)
squares.append(row)
return squares
def subscribe(self, object: Object):
self.objects.append(object)
def loop(self):
self.running = True
while self.running:
self.action()
self.redraw()
def quit(self):
self.running = False
def action(self):
self.user.handler(self)
conditionals = [
not self.user.obj.collide_test(self.user.obj),
all([not o.collide_test(self.user.obj) for o in self.objects])
]
if all(conditionals):
self.user.obj.dampState()
else:
self.user.obj.rollbackState()
self.user.obj.goal_test(self)
def redraw(self):
self.screen.fill((255, 255, 255))
for row in self.squares:
for square_rect in row:
pygame.draw.rect(self.screen, (0, 0, 0), square_rect, 1)
for o in self.objects:
o.blit(self.screen)
self.user.obj.blit(self.screen)
for s in self.state.path:
s.blit(self.screen)
pygame.display.flip()

60
src/StateController.py Normal file
View File

@ -0,0 +1,60 @@
from .obj.TemporaryState import TemporaryState
class StateController:
def __init__(self, istate):
self.path = []
self.explored = []
self.fringe = []
self.istate = istate
def reset(self):
self.path.clear()
self.explored.clear()
self.fringe.clear()
def build_path(self, goal_state):
self.path.append(goal_state)
while self.path[-1].agent_role != "blank":
self.path.append(self.path[-1].parent)
return self.path
def graphsearch(self, engine): # BFS
print("Search path")
self.reset()
self.fringe.append(TemporaryState(self.istate))
while self.fringe:
self.explored.append(self.fringe.pop(0))
if self.explored[-1].goal_test(engine):
print("Goal!")
goal_state = self.explored[-1]
self.reset()
return self.build_path(goal_state)
self.succ(self.explored[-1].front(), engine)
self.succ(self.explored[-1].left(), engine)
self.succ(self.explored[-1].right(), engine)
self.path = self.fringe
engine.redraw()
self.reset()
print("Not found")
return False
def succ(self, state, engine):
if state.collide_test():
return
elif any(e.compare(state) for e in self.explored):
return
elif any([o.collide_test(state) for o in engine.objects]):
return
self.fringe.append(state)

20
src/UserController.py Normal file
View File

@ -0,0 +1,20 @@
import pygame
class UserController:
def __init__(self, usrObj):
self.obj = usrObj
def handler(self, engine):
for event in pygame.event.get():
if event.type == pygame.QUIT:
engine.quit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
self.obj.front()
elif event.key == pygame.K_LEFT:
self.obj.left()
elif event.key == pygame.K_RIGHT:
self.obj.right()
elif event.key == pygame.K_SPACE:
engine.state.graphsearch(engine)

BIN
src/img/blank.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

BIN
src/img/block.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
src/img/front.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

BIN
src/img/left.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
src/img/right.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

9
src/obj/Block.py Normal file
View File

@ -0,0 +1,9 @@
from src.obj.Object import Object
class Block(Object):
def __init__(self, position, orientation, square_size, square_count):
super().__init__("block", position, orientation, square_size, square_count)
def collide_test(self, waiter: Object) -> bool:
return waiter.position == self.position

20
src/obj/Kitchen.py Normal file
View File

@ -0,0 +1,20 @@
from src.obj.Object import Object
class Kitchen(Object):
def __init__(self, position, orientation, square_size, square_count):
super().__init__("kitchen", position, orientation, square_size, square_count)
def goal_test(self, waiter) -> bool:
conditions = [
waiter.orders_in_basket(),
self.position == waiter.position
]
if all(conditions):
for table in waiter.basket:
if table.agent_role == "wait":
table.next_role(waiter)
return True
return False

47
src/obj/Object.py Normal file
View File

@ -0,0 +1,47 @@
import pygame
class Object:
def __init__(self, agent_role, position, orientation, square_size, square_count):
self.agent_role = agent_role
self.position = position
self.orientation = orientation % 4
self.square_size = square_size
self.square_count = square_count
self.image = pygame.image.load(
'src/img/{0}.png'.format(self.agent_role))
self.image = pygame.transform.scale(
self.image, (self.square_size, self.square_size))
self.rect = pygame.Rect(position[0] * square_size,
position[1] * square_size,
square_size, square_size)
def change_role(self, new_role):
self.agent_role = new_role
self.image = pygame.image.load('src/img/{0}.png'.format(new_role))
self.image = pygame.transform.scale(
self.image, (self.square_size, self.square_size))
def get_angle(self):
'''
orientation = 0 -> up\n
orientation = 1 -> left\n
orientation = 2 -> down\n
orientation = 3 -> right\n
'''
return self.orientation * 90
def collide_test(self, obj) -> bool:
return False
def goal_test(self, waiter) -> bool:
return False
def blit(self, screen: pygame.Surface):
image = pygame.transform.rotate(self.image, self.get_angle())
self.rect.x = self.position[0] * self.square_size
self.rect.y = self.position[1] * self.square_size
screen.blit(image, self.rect)

24
src/obj/Table.py Normal file
View File

@ -0,0 +1,24 @@
from src.obj.Object import Object
class Table(Object):
def __init__(self, position, orientation, square_size, square_count, current_role=1):
super().__init__("table", position, orientation, square_size, square_count)
self.roles = ["table", "order", "wait", "done"]
self.current_role = current_role
self.change_role(self.roles[self.current_role])
def next_role(self, waiter):
if waiter.agent_role == "waiter":
self.current_role = (self.current_role + 1) % 4
self.change_role(self.roles[self.current_role])
def goal_test(self, waiter) -> bool:
if self.position == waiter.position:
if self.agent_role == "order":
return waiter.take_order(self)
elif self.agent_role == "done":
return waiter.drop_order(self)
return False

51
src/obj/TemporaryState.py Normal file
View File

@ -0,0 +1,51 @@
from src.obj.Waiter import Waiter
import copy
class TemporaryState(Waiter):
def __init__(self, parent, action="blank"):
super().__init__(copy.deepcopy(parent.position),
copy.copy(parent.orientation),
copy.copy(parent.square_size),
copy.copy(parent.square_count),
copy.copy(parent.basket))
self.parent = parent
self.change_role(action)
self.apply_transformation()
def apply_transformation(self):
if self.agent_role == "left":
self.orientation = (self.orientation + 1) % 4
elif self.agent_role == "right":
self.orientation = (self.orientation - 1) % 4
elif self.agent_role == "front":
if self.orientation % 2: # x (1 or 3)
self.position[0] += self.orientation - 2 # x (-1 or +1)
else: # y (0 or 2)
self.position[1] += self.orientation - 1 # y (-1 or +1)
def left(self):
return TemporaryState(self, "left")
def right(self):
return TemporaryState(self, "right")
def front(self):
return TemporaryState(self, "front")
def collide_test(self) -> bool:
out_of_range = [
self.position[0] >= self.square_count,
self.position[1] >= self.square_count,
self.position[0] < 0,
self.position[1] < 0
]
return any(out_of_range)
def compare(self, state) -> bool:
conditions = [
self.position == state.position,
self.orientation == state.orientation
]
return all(conditions)

62
src/obj/Waiter.py Normal file
View File

@ -0,0 +1,62 @@
import copy
from src.obj.Object import Object
class Waiter(Object):
def __init__(self, position, orientation, square_size, square_count, basket=[]):
super().__init__("waiter", position, orientation, square_size, square_count)
self.basket_size = 2
self.basket = basket
self.prev_position = copy.deepcopy(self.position)
self.prev_orientation = copy.copy(self.orientation)
def dampState(self):
self.prev_position = copy.deepcopy(self.position)
self.prev_orientation = copy.copy(self.orientation)
def rollbackState(self):
self.position = copy.deepcopy(self.prev_position)
self.orientation = copy.copy(self.prev_orientation)
def take_order(self, table) -> bool:
if table.agent_role == "order":
if len(self.basket) < self.basket_size:
table.next_role(self)
self.basket.append(table)
return True
return False
def drop_order(self, table) -> bool:
if table.agent_role == "done":
self.basket.remove(table)
table.next_role(self)
return True
return False
def orders_in_basket(self) -> bool:
return self.basket
def left(self):
self.orientation = (self.orientation + 1) % 4
def right(self):
self.orientation = (self.orientation - 1) % 4
def front(self):
if self.orientation % 2: # x (1 or 3)
self.position[0] += self.orientation - 2 # x (-1 or +1)
else: # y (0 or 2)
self.position[1] += self.orientation - 1 # y (-1 or +1)
def collide_test(self, obj) -> bool:
out_of_range = [
self.position[0] >= self.square_count,
self.position[1] >= self.square_count,
self.position[0] < 0,
self.position[1] < 0
]
return any(out_of_range)
def goal_test(self, engine):
return any([o.goal_test(self) for o in engine.objects])