Merge pull request 'saving and loading map parser prototype' (#4) from prototype_saves into master

Reviewed-on: #4
Reviewed-by: BrokenBrad <eddyjiofak@gmail.com>
Reviewed-by: Mat_02 <diletomatteo@gmail.com>
This commit is contained in:
Mat_02 2023-02-20 15:21:46 +01:00
commit dfccb65f02
2 changed files with 232 additions and 0 deletions

View File

@ -0,0 +1,35 @@
# Prototypes Saves files
This prototype is aiming at defining a structure for saving "map" files.
The objective is to have it quite modular so that if we want to add more level we can easily do so.
Another objective is that theses files are as small as possible without missing information.
## What needs to be saved
We need to have the shape of the map itself and the shape and number of each pieces it is a minimum
I also want to define an header (and maybe a footer) to the file so that it is easier to recover if any corruption occure to a drive
(we all been trough that)
## Method
I would like to save a file as byte so I need to have a bitewise parser. This is the objective of this prototype
## Structure
The map file should have the following structure:
- The file should be named <map_name>.shmap (where shmap stand for shape map)
- The file should start with the ascii characters S, M then S (0x534D53)
- The file should end with the ascii characters S, M then E (0x534D45)
- First we define the map shape
1) the map should always be a square. the lenght of this square will be the first octet after the header.
2) next we have s x s (where s is the size of the square) bits (1/0) where
- 0 represent an empty cell (where we can place a shape)
- 1 represent a filled cell (where we can't place a shape)
- Next we define the amount of shape for this level with a number on one octet
- Next we define each shapes with the same method defined previously for a map
With all that we should have all that is needed for a level to work... further information could be added later this is just a prototype atm

View File

@ -0,0 +1,197 @@
#!/bin/python
import os
class MapNotSquareException(Exception):
"""
Matrix used is not a Square and cannot be interpretted as a piece
"""
class PieceNotSquareException(Exception):
"""
Matrix used is not a Square and cannot be interpretted as a piece
"""
class SaveParser:
"""
Parser for the game file
"""
def __init__(self):
self.map_shape = [[0]]
self.pieces = []
def define_map(self, map_shape):
size = len(map_shape)
for row in map_shape:
if size != len(row):
raise MapNotSquareException
self.map_shape = map_shape
def add_piece(self, piece_shape):
size = len(piece_shape)
for row in piece_shape:
if size != len(row):
raise PieceNotSquareException
self.pieces.append(piece_shape)
def __str__(self):
return str(self.map_shape)
def shape_to_bytes(self, shape_matrix):
shape_list = [b for r in shape_matrix for b in r]
byte_ammount = len(shape_list) // 8 + 1
tray = 0
for i in shape_list:
tray = (tray << 1) | i
return tray.to_bytes(byte_ammount, 'big')
def bytes_to_shape(self, bytes_list, map_size):
list_octet = []
for octet in bytes_list:
octet_data = list(f"{octet:08b}")
[list_octet.append(d) for d in octet_data]
list_octet = list_octet[-(map_size**2):]
matrix = []
for i in range(map_size):
matrix.append([])
for j in range(map_size):
matrix[i].append(list_octet.pop(0))
return matrix
def load(self, filename):
"""
load the file and prepare to parse informations
"""
with open(filename, mode='br') as file:
data = list(file.read())
data_pos = [0, 0]
for i in range(len(data)):
if data[i] == 83 and data[i+1] == 77 and data[i+2] == 83: # SMS
data_pos[0] = i+3
break
for i in range(data_pos[0], len(data)):
if data[i] == 83 and data[i+1] == 77 and data[i+2] == 69: # SME
data_pos[1] = i
break
map_data = data[data_pos[0]:data_pos[1]]
self.define_map(self.bytes_to_shape(map_data[1:((map_data[0]**2)//8)+2], map_data[0]))
map_data = map_data[(map_data[0]**2) // 8 + 2:]
pieces_ammount = map_data.pop(0)
for i in range(pieces_ammount):
print(map_data)
piece_size = map_data.pop(0)
print(piece_size)
piece_data = map_data[:(piece_size**2) // 8 + 1]
print(piece_data)
map_data = map_data[(map_data[0]**2) // 8 + 2:]
print(map_data)
self.add_piece(self.bytes_to_shape(piece_data, piece_size))
def save(self, filename):
"""
save parsed info to the file
"""
save_data = b''
save_data += b'SMS'
save_data += len(self.map_shape).to_bytes(1, 'big')
save_data += self.shape_to_bytes(self.map_shape)
save_data += len(self.pieces).to_bytes(1, 'big')
for piece in self.pieces:
save_data += len(piece).to_bytes(1, 'big')
save_data += self.shape_to_bytes(piece)
save_data += b'SME'
with open(filename, mode='bw') as file:
file.write(save_data)
def show_matrix(matrix, highlight: tuple = None):
"""
:matrix: matrix to draw
:highlight: tuple of the coordinates to hightligh
"""
size = len(matrix)
h_x, h_y = None, None
if highlight:
h_x, h_y = highlight
if size != len(matrix[0]):
print("ERROR: The matrix is not square")
return
line_str = "+" + ''.join(['-+' for _ in range(size)])
for k, x in enumerate(matrix):
print(line_str)
print('|', end="")
for l, y in enumerate(x):
if k == h_x and l == h_y:
print("\033[42m", end="")
print(str(y) + "\033[00m" + '|', end="")
print()
print(line_str)
def cls():
'clear the screen'
for _ in range(os.get_terminal_size()[1]):
print()
def menu(P):
"""draw a simple menu to test the SaveParser class"""
print("1) define terrain")
print("2) add a piece")
print("3) show data")
print("4) save data")
print("5) load data")
print("6) quit")
item = int(input("Select an option :"))
cls()
match item:
case 1:
size = int(input("what is the size of the map: "))
P.map_shape = [[0 for _ in range(size)] for _ in range(size)]
for i in range(size):
for j in range(size):
cls()
show_matrix(P.map_shape, (i,j))
P.map_shape[i][j] = int(input("0: empty; 1: filled :"))
case 2:
size = int(input("what is the size of the piece: "))
temp = [[0 for _ in range(size)] for _ in range(size)]
for i in range(size):
for j in range(size):
cls()
show_matrix(temp, (i,j))
temp[i][j] = int(input("0: empty; 1: filled :"))
P.add_piece(temp)
case 3:
if P.map_shape:
print("map:")
show_matrix(P.map_shape)
for i, v in enumerate(P.pieces):
print()
print(f"piece {i+1}")
show_matrix(v)
case 4:
filename = input('enter the file name (default: default.smap):')
filename = filename if filename else "default.smap"
P.save(filename)
case 5:
filename = input('enter the file name (default: default.smap):')
filename = filename if filename else "default.smap"
P.load(filename)
case 6:
return False
return True
if __name__ == "__main__":
cls()
P = SaveParser()
while menu(P):
pass