2019-11-10 18:41:05 +01:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
from enum import IntEnum
|
|
|
|
|
from copy import deepcopy
|
2019-11-13 11:28:29 +01:00
|
|
|
|
from PIL import Image
|
|
|
|
|
import os
|
2019-11-10 18:41:05 +01:00
|
|
|
|
|
|
|
|
|
class DotTile(IntEnum):
|
|
|
|
|
NDT = 0 # no dot
|
|
|
|
|
SPD = 1 # small pac-dot
|
|
|
|
|
BPD = 2 # big pac-dot
|
2019-11-13 11:28:29 +01:00
|
|
|
|
FRT = 3 # fruit
|
2019-11-10 18:41:05 +01:00
|
|
|
|
|
|
|
|
|
class PhysTile(IntEnum):
|
|
|
|
|
GRD = 0 # ground
|
|
|
|
|
WAL = 1 # wall
|
|
|
|
|
GSD = 2 # ghost door
|
2019-11-13 11:28:29 +01:00
|
|
|
|
GWL = 3 # ghost cell wall
|
|
|
|
|
GCF = 4 # ghost cell floor
|
|
|
|
|
TPT = 5 # teleporter tile
|
|
|
|
|
FIT = 6 # fully inaccessible tile
|
2019-11-10 18:41:05 +01:00
|
|
|
|
# ghost-cell ground as FIT ? verify no pac-dot here
|
|
|
|
|
|
2019-11-15 19:03:57 +01:00
|
|
|
|
class CrossTile(IntEnum):
|
|
|
|
|
# code where we can find another GRD/TPT Tile from a given Tile
|
|
|
|
|
# with binary masks
|
|
|
|
|
UP = 1 # 0001
|
|
|
|
|
DOWN = 2 # 0010
|
|
|
|
|
LEFT = 4 # 0100
|
|
|
|
|
RIGHT = 8 # 1000
|
|
|
|
|
# direction changement if != 3 or != 12
|
|
|
|
|
|
2019-11-10 18:41:05 +01:00
|
|
|
|
class Map:
|
|
|
|
|
"""
|
|
|
|
|
Pacman maps size is 28×31
|
|
|
|
|
"""
|
|
|
|
|
|
2019-11-15 11:19:57 +01:00
|
|
|
|
width = 28
|
|
|
|
|
height = 31
|
2019-11-13 11:28:29 +01:00
|
|
|
|
# tile_size = 16 # tile subdivision for dynamic movement
|
2019-11-10 18:41:05 +01:00
|
|
|
|
|
2019-11-13 11:28:29 +01:00
|
|
|
|
def __init__(self, phys_map = [], dots_map = [], maze_img_file=""):
|
2019-11-10 18:41:05 +01:00
|
|
|
|
"""
|
|
|
|
|
physic_map is the array containing elements:
|
|
|
|
|
0: ground tile
|
|
|
|
|
1: wall
|
|
|
|
|
2: ghost door
|
|
|
|
|
3: teleporter (no need to precise which to go because we assume it will
|
|
|
|
|
be at the opposite map tile)
|
|
|
|
|
4: fully inaccessible tile (basically tiles that represent the "in-wall"
|
|
|
|
|
space)
|
|
|
|
|
|
|
|
|
|
dots_map is a layer on top of the physic_map of the same dimension which contains
|
|
|
|
|
0: no dot
|
|
|
|
|
1: small pac-dot
|
|
|
|
|
2: big pac-dot
|
|
|
|
|
"""
|
2019-11-13 11:28:29 +01:00
|
|
|
|
if maze_img_file and os.path.isfile(maze_img_file):
|
2019-11-13 20:23:35 +01:00
|
|
|
|
print('coucou')
|
2019-11-13 11:28:29 +01:00
|
|
|
|
try:
|
|
|
|
|
self.phys_map = decode_map(maze_img_file)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
raise e
|
2019-11-13 20:23:35 +01:00
|
|
|
|
else:
|
|
|
|
|
self.phys_map = phys_map # in the first part we assume phys_map is correct and no need to verify
|
|
|
|
|
self.dots_map = dots_map
|
2019-11-10 18:41:05 +01:00
|
|
|
|
self.intersect_map = [] # TODO - the layer which contains intersections pre-calculated
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def verify(self, phys_map, dots_map) -> bool:
|
|
|
|
|
"""
|
|
|
|
|
This method will verify if a given map is valid or not
|
|
|
|
|
Return True if correct else False
|
|
|
|
|
|
|
|
|
|
we will assume
|
|
|
|
|
there are only:
|
|
|
|
|
- 1 ghost door
|
|
|
|
|
- 4 big pac-dots
|
|
|
|
|
- 240 pac-dots
|
|
|
|
|
|
|
|
|
|
Each ground tile must have at least 2 ground tile neighboors because
|
|
|
|
|
there is no dead-end tile in pac-man
|
|
|
|
|
Each dot must be on a ground tile (not even a teleporter one)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if not (len(phys_map) == len(dots_map) == 31):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
for i in range(len(phys_map)):
|
|
|
|
|
if not (len(phys_map[i]) == len(dots_map[i]) == 28):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# 1 ghost door verification
|
|
|
|
|
if sum(sub_arr.count(PhysTile.GSD) for sub_arr in phys_map) != 1:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# # 4 big pac dots
|
|
|
|
|
if sum(sub_arr.count(DotTile.BPD) for sub_arr in dots_map) != 4:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# # 240 small pac-dots
|
|
|
|
|
if sum(sub_arr.count(DotTile.SPD) for sub_arr in dots_map) != 240:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# dots are only in ground tile
|
|
|
|
|
for row in range(len(phys_map)):
|
|
|
|
|
for col in range(len(phys_map[0])):
|
|
|
|
|
if dots_map[row][col] and phys_map[row][col]: # no dot = 0; ground = 0
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# odd number of teleporter tiles
|
|
|
|
|
teleporter_count = sum(sub_arr.count(PhysTile.TPT) for sub_arr in phys_map)
|
|
|
|
|
if teleporter_count % 2:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
edges = phys_map[0][1:] + phys_map[-1][1:] + [sub[0] for sub in phys_map] \
|
|
|
|
|
+ [sub[-1] for sub in phys_map][1:-1]
|
|
|
|
|
|
|
|
|
|
# no ground tile on edges
|
|
|
|
|
if PhysTile.GRD in edges:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# teleporters are in front of the other
|
|
|
|
|
for col in range(1, len(phys_map[0])-1):
|
|
|
|
|
if phys_map[0][col] == PhysTile.TPT or phys_map[-1][col] == PhysTile.TPT:
|
|
|
|
|
if phys_map[0][col] == phys_map[-1][col]:
|
|
|
|
|
teleporter_count -= 2
|
|
|
|
|
else:
|
|
|
|
|
return False
|
|
|
|
|
for row in range(1, len(phys_map)-1):
|
|
|
|
|
if phys_map[row][0] == PhysTile.TPT or phys_map[row][-1] == PhysTile.TPT:
|
|
|
|
|
if phys_map[row][0] == phys_map[row][-1]:
|
|
|
|
|
teleporter_count -= 2
|
|
|
|
|
else:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
if teleporter_count: # not all teleporters are on edges
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# no teleporter on corners
|
|
|
|
|
if any(PhysTile.TPT in (phys_map[0][0], phys_map[0][-1],
|
|
|
|
|
phys_map[self.height-1][0], phys_map[-1][-1])):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# now we have to verify there is no dead-end ground tile
|
|
|
|
|
for col in range(1, self.width-1):
|
|
|
|
|
for row in range(1, self.height-1):
|
|
|
|
|
cpt = 0
|
|
|
|
|
for x, y in ((0, 1), (1, 0), (-1, 0), (0, -1)):
|
|
|
|
|
if phys_map[row+y][col+x] in (PhysTile.GRD, PhysTile.TPT):
|
|
|
|
|
cpt += 1
|
|
|
|
|
if cpt == 2:
|
|
|
|
|
break
|
|
|
|
|
if cpt < 2:
|
|
|
|
|
return False
|
|
|
|
|
# we have to verify if there is only 1 connexity component of (PhysTile.GRD, PhysTile.TPT, PhysTile.BPD, PhysTile.SPD)
|
|
|
|
|
if not connex(phys_map):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
2019-11-13 18:01:59 +01:00
|
|
|
|
def get_tile(self, x, y):
|
|
|
|
|
return self.phys_map[y][x]
|
2019-11-15 19:03:57 +01:00
|
|
|
|
|
|
|
|
|
def create_cross_layer(self):
|
|
|
|
|
dictionnary = {
|
|
|
|
|
(0, 1):CrossTile.DOWN,
|
|
|
|
|
(0, -1): CrossTile.UP,
|
|
|
|
|
(1, 0): CrossTile.RIGHT,
|
|
|
|
|
(-1, 0): CrossTile.LEFT
|
|
|
|
|
}
|
|
|
|
|
for row in range(self.height):
|
|
|
|
|
self.intersect_map.append([])
|
|
|
|
|
for col in range(self.width):
|
|
|
|
|
if not self.get_tile(col, row) in (PhysTile.GRD, PhysTile.TPT):
|
|
|
|
|
self.intersect_map[-1].append(0)
|
|
|
|
|
if self.get_tile(col, row) in (PhysTile.GRD, PhysTile.TPT):
|
|
|
|
|
cpt = 0
|
|
|
|
|
for dir in dictionnary:
|
|
|
|
|
test_col = (col + dir[0]) % self.width
|
|
|
|
|
test_row = (row + dir[1]) % self.height
|
|
|
|
|
if self.get_tile(test_col, test_row) in (PhysTile.GRD, PhysTile.TPT):
|
|
|
|
|
cpt |= dictionnary[dir]
|
|
|
|
|
self.intersect_map[-1].append(cpt)
|
|
|
|
|
|
2019-11-13 18:01:59 +01:00
|
|
|
|
|
2019-11-10 18:41:05 +01:00
|
|
|
|
def explore(matrix, x=-1, y=-1):
|
|
|
|
|
"""explore the given matrix and change it (GRD and TPT become FIT)"""
|
|
|
|
|
if x < 0 and y < 0:
|
|
|
|
|
# searching the beginning
|
|
|
|
|
for row in range(len(matrix)):
|
|
|
|
|
for col in range(len(matrix[0])):
|
|
|
|
|
if matrix[row][col]:
|
|
|
|
|
explore(matrix, col, row)
|
|
|
|
|
elif matrix[y][x] in (PhysTile.TPT, PhysTile.GRD):
|
|
|
|
|
matrix[y][x] = PhysTile.FIT
|
|
|
|
|
for col, row in ((0, 1), (1, 0), (0, -1), (-1, 0)):
|
|
|
|
|
explore(matrix, (x+col)%len(matrix[0]), (y+row)%len(matrix))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def connex(matrix, x=-1, y=-1):
|
|
|
|
|
"""return True if the matrix is connex - has only one connexity part"""
|
|
|
|
|
temp = deepcopy(matrix)
|
|
|
|
|
explore(temp)
|
|
|
|
|
if any(tile in (PhysTile.GRD, PhysTile.TPT) for row in temp for tile in row):
|
|
|
|
|
return False
|
|
|
|
|
return True
|
2019-11-13 11:28:29 +01:00
|
|
|
|
|
|
|
|
|
def decode_map(img_file):
|
|
|
|
|
img = Image.open(img_file)
|
|
|
|
|
dictionnary = {
|
|
|
|
|
(0 , 0, 0): PhysTile.GRD,
|
|
|
|
|
(255, 0, 255): PhysTile.GCF,
|
|
|
|
|
(0 , 0, 255): PhysTile.WAL,
|
|
|
|
|
(0 , 255, 0): PhysTile.GSD,
|
|
|
|
|
(0 , 255, 255): PhysTile.GWL,
|
|
|
|
|
(255, 0, 0): PhysTile.TPT,
|
|
|
|
|
(255, 255, 0): PhysTile.FIT,
|
|
|
|
|
}
|
|
|
|
|
data = list(img.getdata())
|
|
|
|
|
matrix = []
|
|
|
|
|
for row in range(img.height):
|
|
|
|
|
matrix.append([])
|
|
|
|
|
for col in range(img.width):
|
|
|
|
|
try:
|
|
|
|
|
tile = dictionnary[data[col + row*img.width][:3]] # avoid alpha component
|
|
|
|
|
except:
|
|
|
|
|
raise ValueError("Pixel " + str(col) + "," + str(row) + " is invalid")
|
|
|
|
|
matrix[-1].append(tile)
|
2019-11-15 19:03:57 +01:00
|
|
|
|
return matrix
|
|
|
|
|
|
|
|
|
|
def get_one_in_digit(number):
|
|
|
|
|
cpt = 0
|
|
|
|
|
while number:
|
|
|
|
|
cpt += number & 1
|
|
|
|
|
number //= 2
|
|
|
|
|
return cpt
|