diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/InteligentnySaper.iml b/.idea/InteligentnySaper.iml new file mode 100644 index 0000000..74d515a --- /dev/null +++ b/.idea/InteligentnySaper.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8e1d7b1 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..55dbd56 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/classes/ai.py b/classes/ai.py index b25f94e..98d3614 100644 --- a/classes/ai.py +++ b/classes/ai.py @@ -1,5 +1,5 @@ import pygame -from classes import minesweeper, system +from classes import minesweeper, system, bfs from random import randrange class AI: @@ -18,15 +18,34 @@ class AI: #co ma zrobić tylko na początku def ready(self): self.saper.set_map(self.current_map) + + goal_state = [minesweeper.Map.mines[0].position_x, minesweeper.Map.mines[0].position_y] + print(goal_state) + + find_path = bfs.BFS(self.saper) + find_path.graphsearch([], [], bfs.BFS.successor, goal_state) #co ma robić przy każdym FPS'ie - def update(self): + def updateFPS(self): + pass + + #co ma zrobić przy każdym ruchu <------------------------- najważniejsze + def updateTile(self): + #aktualne pola (do debugu) + sensor = self.saper.sensor() + #print(sensor[0]) + #print(sensor[1]) + #print(sensor[2]) + #print("-------") + + #podniesienie bomby jeśli jest jakaś na tym polu + self.saper.pick_up() + + #poruszenie się if self.user_controlled: self.minesweeper_controls() return - - self.chaos_controls() - #TU pisać resztę + self.chaos_controls() # <--------------------------zamiast tego trzeba wstawić jakiś algorytm def minesweeper_controls(self): @@ -43,10 +62,11 @@ class AI: def chaos_controls(self): dir = randrange(4) if dir==0: - self.saper.move(0) + self.saper.rotate("N") elif dir==1: - self.saper.move(180) + self.saper.rotate("S") elif dir==2: - self.saper.move(270) + self.saper.rotate("W") elif dir==3: - self.saper.move(90) \ No newline at end of file + self.saper.rotate("E") + self.saper.move() \ No newline at end of file diff --git a/classes/bfs.py b/classes/bfs.py new file mode 100644 index 0000000..106896f --- /dev/null +++ b/classes/bfs.py @@ -0,0 +1,98 @@ +import heapq # dla utrzymania fringe +from classes import node, minesweeper +import time + + +class BFS: + + agent: minesweeper.Minesweeper + node: node.Node + + def __init__(self, agent): + self.agent = agent + + def successor(self, current_position): + new_nodes = [] + neighbours_list = self.agent.sensor(current_position[0], current_position[1]) + + + if neighbours_list[1][0] not in ['wall', 'cliff_south', 'cliff_east', 'cliff_north', 'cliff_west']: + new_nodes.append([1 + current_position[0] - 1, 0 + current_position[1] - 1]) + if neighbours_list[2][1] not in ['wall', 'cliff_south', 'cliff_east', 'cliff_north', 'cliff_west']: + new_nodes.append([2 + current_position[0] - 1, 1 + current_position[1] - 1]) + if neighbours_list[1][2] not in ['wall', 'cliff_south', 'cliff_east', 'cliff_north', 'cliff_west']: + new_nodes.append([1 + current_position[0] - 1, 2 + current_position[1] -1]) + if neighbours_list[0][1] not in ['wall', 'cliff_south', 'cliff_east', 'cliff_north', 'cliff_west']: + new_nodes.append([0 + current_position[0] - 1, 1 + current_position[1] - 1]) + + return new_nodes + + + + # fringe = struktura danych przeszowyjąca wierchowki do odwiedzenia + # explored = lista odwiedzonych stanow + # position_at_beginning = stan poczatkowy + # succ = funkcja nastempnika + # goaltest = test spewnienia celu + + def graphsearch(self, fringe, explored, succ, goaltest): + + positiont_at_beginning = [self.agent.position_x, self.agent.position_y, self.agent.rotation_degrees] # x, y, gdzie_patczy + final_action_list = [] + root = node.Node(None, None, positiont_at_beginning[:2]) # parent, action, position + counter = 0 + heapq.heappush(fringe, (counter, root)) + visited_position = [] + + + while len(fringe) != 0: + flag = True + + + if len(fringe) == 0: + return False + break + + tmp_node = heapq.heappop(fringe) # node + #print(f'my parent is {tmp_node[1].get_parent()}') + tmp_node_position = tmp_node[1].get_position() + + print(f'Position of our node {tmp_node_position[:2]}') + + visited_position.append(tmp_node_position[:2]) + #print(f'visited position: {visited_position}') + + if tmp_node_position[:2] == goaltest: + print(' we find node') + # print(visited_position) + + print(fringe) + #break + while tmp_node[1].get_parent() is not None: + #print('sdfhdfg') + #print(tmp_node[1].get_parent()) + final_action_list.append(tmp_node[1].get_action()) + #print(final_action_list) + tmp_node = tmp_node[1].get_parent() + final_action_list = reversed(final_action_list) + print(final_action_list) + break + + # return final_action_list + + + explored.append(tmp_node) + + neighbours_list_of_our_node = self.successor(tmp_node_position[:2]) + # print(neighbours_list_of_our_node) + for node_ in neighbours_list_of_our_node: + # jesli pozucja wezla nie jest w fringe i nie jest w explored + if [node_[0], node_[1]] not in visited_position and node_[0] >= 0 and node_[1] >=0: + counter += 1 + x = node.Node(tmp_node, None, [node_[0], node_[1]]) # action + heapq.heappush(fringe, (counter, x)) + # time.sleep(0.5) + + + + diff --git a/classes/minesweeper.py b/classes/minesweeper.py index 241a4b4..e591683 100644 --- a/classes/minesweeper.py +++ b/classes/minesweeper.py @@ -201,6 +201,7 @@ class NPC: #saper class Minesweeper: size:int + rotation_degrees:int position_x:int position_y:int image:pygame.surface.Surface @@ -223,6 +224,7 @@ class Minesweeper: self.image = pygame.image.load("assets/sprites/saper_fun_sized.png") self.image = pygame.transform.scale(self.image, (self.size, self.size)) self.rotated_image = self.image + self.rotation_degrees=0 def set_map(self, map:Map): self.current_map = map @@ -244,6 +246,8 @@ class Minesweeper: finished=True elif self.offset_y<0: self.offset_y+=dist + if self.offset_y<-self.size and self.offset_y>-1.2*self.size: + pygame.mixer.Channel(1).play(pygame.mixer.Sound("assets/sounds/ledge.wav")) if self.offset_y>=0: finished=True @@ -258,7 +262,24 @@ class Minesweeper: window.blit(self.rotated_image, position_on_screen) self.update_offset(delta) - def move(self, dir:int): + def rotate(self, dir:str): + dirr=0 + if dir=="N": + dirr=180 + elif dir=="S": + dirr=0 + elif dir=="W": + dirr=270 + elif dir=="E": + dirr=90 + else: + return + + self.rotation_degrees=dirr + self.rotated_image = pygame.transform.rotate(self.image, dirr) + + + def move(self, dir:int=-1): #południe - 0 #wschód - 90 #północ - 180 @@ -266,7 +287,11 @@ class Minesweeper: if self.offset_x!=0 or self.offset_y!=0: return - self.rotated_image = pygame.transform.rotate(self.image, dir) + if dir==-1: + dir = self.rotation_degrees + else: + self.rotation_degrees=dir + self.rotated_image = pygame.transform.rotate(self.image, dir) move_legal=True cliff_jump=False @@ -283,11 +308,9 @@ class Minesweeper: if next_x == self.current_map.tiles_x or next_x == -1: move_legal=False - if next_y == self.current_map.tiles_y or next_y == -1: + elif next_y == self.current_map.tiles_y or next_y == -1: move_legal=False - if self.current_map.terrain_matrix[next_y][next_x]>9: - move_legal=False - if self.current_map.terrain_matrix[next_y][next_x]>9: + elif self.current_map.terrain_matrix[next_y][next_x]>9: move_legal=False for cliff in self.current_map.cliffs: @@ -335,6 +358,38 @@ class Minesweeper: def drop_civilians(self): pass - def sensor(self): - sensor_list = [[],[],[]] + def sensor(self, x:int=-1, y:int=-1): + if x==-1: + x = self.position_x + if y==-1: + y = self.position_y + sensor_list = [["","",""],["","",""],["","",""]] + for i in range(3): + for j in range(3): + posx = x-1+j + posy = y-1+i + if posx >= self.current_map.tiles_x or posx <= -1: + sensor_list[i][j]="wall" + elif posy >= self.current_map.tiles_y or posy <= -1: + sensor_list[i][j]="wall" + elif self.current_map.terrain_matrix[posy][posx]>9: + sensor_list[i][j]="wall" + else: + sensor_list[i][j]="sand" + for cliff in self.current_map.cliffs: + if (posx, posy) == (cliff.position_x, cliff.position_y): + if cliff.rotation==0: + sensor_list[i][j]="cliff_south" + elif cliff.rotation==90: + sensor_list[i][j]="cliff_east" + elif cliff.rotation==180: + sensor_list[i][j]="cliff_north" + elif cliff.rotation==270: + sensor_list[i][j]="cliff_west" + break + for mine in self.current_map.mines: + if (posx, posy) == (mine.position_x, mine.position_y): + sensor_list[i][j]="mine" + break + return sensor_list \ No newline at end of file diff --git a/classes/node.py b/classes/node.py new file mode 100644 index 0000000..accc175 --- /dev/null +++ b/classes/node.py @@ -0,0 +1,19 @@ +class Node: + def __init__(self, parent, action, state_array): + self.parent = parent + self.action = action + self.position = state_array + + def get_position(self): + return self.position + + def get_action(self): + return self.action + + def get_parent(self): + return self.parent + + def set_parent(self, parent): + self.parent = parent + + diff --git a/classes/system.py b/classes/system.py index e2faba0..52b67db 100644 --- a/classes/system.py +++ b/classes/system.py @@ -6,12 +6,14 @@ class Window: height:int title:str icon_path:str + paused:bool def __init__(self, width:int=640, height:int=480, title="", icon_path=""): self.set_resolution(width,height) self.set_title(title) self.set_icon(icon_path) self.mount() + self.paused=False def set_resolution(self, width:int, height:int): self.width = width diff --git a/main.py b/main.py index 883b076..0f94171 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,7 @@ import pygame #system - klasy związane z pygame #minesweeper - klasy związane z samym saperem #ai - klasa wykonująca ruchy sapera -from classes import system, minesweeper, ai +from classes import system, minesweeper, ai, bfs #ustalenie wielkości pojedyńczych kawałków mapy, oraz wielkości mapy TILE_SIZE = 64 @@ -11,7 +11,7 @@ TILES_X = int(12) TILES_Y = int(10) #wł/wył muzyki -MUSIC=True +MUSIC=False #ustalenie FPS FPS = 60 @@ -24,6 +24,11 @@ def main(): #utworzenie okna do gry window = system.Window(TILE_SIZE*TILES_X, TILE_SIZE*TILES_Y, "Intelligent Minesweeper", "icon.png") + #utworzenie ekranu pauzy + pause_menu = pygame.Surface((TILE_SIZE*TILES_X, TILE_SIZE*TILES_Y)) + pause_menu.set_alpha(128) + pause_menu.fill((0,0,0)) + #utworzenie objektu mapy, wygenerowanie jej i narysowanie na ekranie map = minesweeper.Map(window, TILE_SIZE, TILES_X, TILES_Y) map.generate() @@ -31,8 +36,9 @@ def main(): #utworzenie sapera saper = minesweeper.Minesweeper(0,0, TILE_SIZE) - - + + + #utworzenie objektu klasy AI AI = ai.AI(window, map, saper) #wykonanie funkcji ready() AI @@ -46,13 +52,20 @@ def main(): delta = clock.tick(FPS) #wykonanie funkcji update() AI - AI.update() + AI.updateFPS() + + if saper.offset_x==0 and saper.offset_y==0: + AI.updateTile() #narysowanie terenu i obiektów map.draw_tiles() map.draw_objects() saper.draw(window.window, delta) + #pauza + if window.paused: + window.window.blit(pause_menu, (0,0)) + #odświeżenie ekranu pygame.display.update()