This repository has been archived on 2019-12-09. You can view files and clone it, but cannot push or open issues or pull requests.
pacman/src/pacmap.py

272 lines
9.7 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
from enum import IntEnum
from copy import deepcopy
from PIL import Image
import pygame
import os
class DotTile(IntEnum):
NDT = 0 # no dot
SPD = 1 # small pac-dot
BPD = 2 # big pac-dot
FRT = 3 # fruit
class PhysTile(IntEnum):
GRD = 0 # ground
WAL = 1 # wall
GSD = 2 # ghost door
GWL = 3 # ghost cell wall
GCF = 4 # ghost cell floor
TPT = 5 # teleporter tile
FIT = 6 # fully inaccessible tile
# ghost-cell ground as FIT ? verify no pac-dot here
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
class Map:
"""
Pacman maps size is 28×31
"""
width = 28
height = 31
# tile_size = 16 # tile subdivision for dynamic movement
def __init__(self, phys_map = [], dots_map = [], maze_img_file=""):
"""
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
"""
self._surf = pygame.Surface((Map.width * 20, Map.height * 20))
self._dot_surf = pygame.Surface((Map.width * 20, Map.height * 20))
if maze_img_file and os.path.isfile(maze_img_file):
try:
self.decode_map(maze_img_file)
except Exception as e:
raise e
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
# self.intersect_map = [] # TODO - the layer which contains intersections pre-calculated
self.create_cross_layer()
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
def get_tile(self, x, y):
return self.phys_map[y][x]
def create_cross_layer(self):
self.intersect_map = []
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)
def draw(self, surf):
surf.blit(self._surf, (0, 0))
# self._dot_surf.fill(surf)
def _draw_dots(self, surf):
for row in len(self.dots_map):
for col in len(self.dots_map[0]):
if self.phys_map[row][col] == PhysTile.GRD and self.dots_map[row][col] == DotTile.SPD:
# petits rond
pass
elif self.phys_map[row][col] == PhysTile.GRD and self.dots_map[row][col] == DotTile.BPD:
# big pac dot
pass
def decode_map(self, 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): DotTile.BPD,
(255, 255, 0): "pacstart",
(255, 255, 255): DotTile.SPD
}
data = list(img.getdata())
self.phys_map, self.dots_map = [], []
for row in range(img.height):
self.phys_map.append([])
self.dots_map.append([])
for col in range(img.width):
try:
color = data[col + row*img.width][:3]
tile = dictionnary[color] # avoid alpha component
if isinstance(tile, PhysTile):
self.phys_map[-1].append(tile)
self.dots_map[-1].append(DotTile.NDT)
if tile not in (PhysTile.WAL, PhysTile.GCF, PhysTile.GSD, PhysTile.GWL):
color = (0, 0, 0) # everything else is ground visually
elif isinstance(tile, DotTile):
self.phys_map[-1].append(PhysTile.GRD)
self.dots_map[-1].append(tile)
color = (0, 0, 0)
else: # pacstart
self.phys_map[-1].append(PhysTile.GRD)
self.dots_map[-1].append(DotTile.NDT)
self.pac_start = (len(self.phys_map), len(self.phys_map[-1]))
color = (0, 0, 0)
self._surf.fill(color, pygame.Rect(col*20, row*20, 20, 20))
except:
raise ValueError("Pixel " + str(col) + "," + str(row) + " is invalid")
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
def get_one_in_digit(number):
cpt = 0
while number:
cpt += number & 1
number //= 2
return cpt