Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3a94ff0
csv file handling class and add line
tal680 Jul 22, 2025
6ebf7f1
csv file handling checking file path
tal680 Jul 22, 2025
39a72e7
csv file handling reading file into matrix
tal680 Jul 22, 2025
7023417
generate_custom_map
08Solly Jul 22, 2025
066a562
generate_custom_map
08Solly Jul 22, 2025
3849124
fixed suffix checking
tal680 Jul 22, 2025
ea1d1aa
started converting to static
tal680 Jul 22, 2025
321ab7a
checking file path in add_line
tal680 Jul 22, 2025
938f89d
made read_as_matrix static
tal680 Jul 22, 2025
9ee16d8
Merge branch 'main' of https://github.com/08Solly/rose-game-engine in…
08Solly Jul 22, 2025
8f6db74
documentation and error handling
tal680 Jul 23, 2025
9d14836
generate_custom_map-2
08Solly Jul 23, 2025
3a0d7dd
changed to match python python_version
tal680 Jul 23, 2025
78335b6
generate_custom_map-3
08Solly Jul 23, 2025
ce0ba71
Added a server to recieve the map csv file from the client
RkeyDev Jul 23, 2025
4f639f7
generate_custom_map-4
08Solly Jul 23, 2025
9b69d10
Merge branch 'main' of https://github.com/08Solly/rose-game-engine in…
08Solly Jul 23, 2025
508d18f
generate_custom_map-5
08Solly Jul 23, 2025
1e3028d
generate_custom_map.csv
08Solly Jul 23, 2025
d406f8c
check_obstacle
08Solly Jul 23, 2025
97e3db9
Remove obsolete custom_map.csv and update track.py to read custom map…
RkeyDev Jul 23, 2025
534e488
Merge branch 'generate_custom_map' of https://github.com/08Solly/rose…
RkeyDev Jul 23, 2025
f05e3d5
Refactor file receiver server and add custom map deletion functionality
RkeyDev Jul 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions fileRecieverServer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import os
from http.server import BaseHTTPRequestHandler
from email.parser import BytesParser
from email.policy import default
import re


UPLOAD_DIR = 'map'
UPLOAD_MAP_NAME = 'custom_map.csv'


def parse_content_disposition(header_value):
parts = header_value.split(';')
disposition = parts[0].strip().lower()
params = {}
for part in parts[1:]:
if '=' in part:
key, val = part.strip().split('=', 1)
params[key.lower()] = val.strip('"')
return disposition, params


class FileRecieverServer(BaseHTTPRequestHandler):

def _set_cors_headers(self):
self.send_header('Access-Control-Allow-Origin', '*') # Allow all origins
self.send_header('Access-Control-Allow-Methods', 'POST, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type')

def do_OPTIONS(self):
self.send_response(204) # No content
self._set_cors_headers()
self.end_headers()

def do_POST(self):
if self.path == '/activateRandomMap':
self.send_response(200)
self._set_cors_headers()
self.end_headers()

src = os.path.join(UPLOAD_DIR, "disabled_custom_map.csv")

if os.path.exists(src):
os.rename(src, "map/custom_map.csv")
self.wfile.write(b"Map activated")
else:
self.send_response(404)
self.end_headers()
self.wfile.write(b"custom_map.csv not found")
return

elif self.path == '/deactivateRandomMap':
active_map_path = os.path.join(UPLOAD_DIR, UPLOAD_MAP_NAME)
if os.path.exists(active_map_path):
os.rename(active_map_path, f"map/disabled_custom_map.csv")

self.send_response(200)
self._set_cors_headers()
self.end_headers()
self.wfile.write(b"Map deactivated")
return

elif self.path == '/upload':
content_type = self.headers.get('Content-Type')
if not content_type or not content_type.startswith('multipart/form-data'):
self.send_response(400)
self._set_cors_headers()
self.end_headers()
self.wfile.write(b"Invalid content type")
return

match = re.search('boundary=(.*)', content_type)
if not match:
self.send_response(400)
self._set_cors_headers()
self.end_headers()
self.wfile.write(b"Missing boundary")
return

content_length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(content_length)

full_message = (
f"Content-Type: {content_type}\r\n\r\n".encode() + body
)
msg = BytesParser(policy=default).parsebytes(full_message)

for part in msg.iter_parts():
disposition_header = part.get('Content-Disposition', '')
disposition, params = parse_content_disposition(disposition_header)

if disposition != 'form-data' or 'filename' not in params:
continue

file_data = part.get_payload(decode=True)

os.makedirs(UPLOAD_DIR, exist_ok=True)
filepath = os.path.join(UPLOAD_DIR, UPLOAD_MAP_NAME)

with open(filepath, 'wb') as f:
f.write(file_data)

self.send_response(200)
self._set_cors_headers()
self.end_headers()
self.wfile.write(b"File uploaded successfully.")
return

self.send_response(400)
self._set_cors_headers()
self.end_headers()
self.wfile.write(b"No valid file part found")

else:
self.send_response(404)
self._set_cors_headers()
self.end_headers()
self.wfile.write(b"Endpoint not found")


def do_GET(self):
self.send_response(405)
self._set_cors_headers()
self.end_headers()
self.wfile.write(b"Use POST for all operations")


29 changes: 28 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import argparse
import asyncio
from http.server import HTTPServer
import logging
import os
import threading

from fileRecieverServer import FileRecieverServer
from rose.engine import server


Expand Down Expand Up @@ -55,5 +59,28 @@ def main():
)


def startServer():
server_address = ('', 8000)
httpd = HTTPServer(server_address, FileRecieverServer)
print("Serving on http://localhost:8000")
httpd.serve_forever()


def deleteCustomMap():
if os.path.exists("map/custom_map.csv"):
os.remove("map/custom_map.csv")
if os.path.exists("map/disabled_custom_map.csv"):
os.remove("map/disabled_custom_map.csv")

if __name__ == "__main__":
main()

threading.Thread(target=startServer,daemon=True).start()
try:
main()
finally:
try:
deleteCustomMap()
except FileNotFoundError:
print("No custom map files to remove.")


6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 75 additions & 0 deletions rose/engine/csv_file_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import csv
from pathlib import Path
from typing import Union

class CsvFileHandler:
@staticmethod
def add_line(file_path: Union[Path, str], row: list[str]) -> bool:
"""
Appends a list of strings as a new row to a CSV file.

Args:
file_path (Path | str): Path to the CSV file.
row (list[str]): List of strings representing a row.

Returns:
bool: True if the row was successfully written, False on failure.
"""
try:
path = Path(file_path)

if not path.exists():
print(f"[ERROR] File '{path}' does not exist.")
return False

if path.suffix.lower() != ".csv":
print(f"[ERROR] File '{path}' is not a CSV file.")
return False

with path.open(mode='a', newline='', encoding='utf-8') as file:
writer = csv.writer(file)
writer.writerow(row)

return True

except OSError as os_err:
print(f"[ERROR] File system error: {os_err}")
return False
except Exception as e:
print(f"[ERROR] Unexpected error while writing to CSV: {e}")
return False

@staticmethod
def read_as_matrix(file_path: Union[Path, str]) -> list[list[str]]:
"""
Reads a CSV file and returns its contents as a matrix (list of rows).

Args:
file_path (Path | str): Path to the CSV file.

Returns:
list[list[str]]: Matrix of strings representing the CSV content.
"""
try:
path = Path(file_path)

if not path.exists():
print(f"[ERROR] File '{path}' does not exist.")
return []

if path.suffix.lower() != ".csv":
print(f"[ERROR] File '{path}' is not a CSV file.")
return []

with path.open(mode='r', newline='', encoding='utf-8') as file:
reader = csv.reader(file)
matrix = [row for row in reader]

return matrix

except OSError as os_err:
print(f"[ERROR] File system error: {os_err}")
return []
except Exception as e:
print(f"[ERROR] Unexpected error while reading from CSV: {e}")
return []
35 changes: 33 additions & 2 deletions rose/engine/track.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import random

import os
from rose.engine import config
from rose.common import obstacles
from rose.engine import csv_file_handler


class Track(object):
def __init__(self, is_track_random=False):
self._matrix = None
self.is_track_random = is_track_random
self.reset()
self.custom_index = 0
self.custom_map = csv_file_handler.CsvFileHandler.read_as_matrix("map/custom_map.csv")

# Game state interface



# Game state interface
def update(self):
"""Go to the next game state"""
self._matrix.pop()
self._matrix.insert(0, self._generate_row())
if os.path.exists("map/custom_map.csv") and self.custom_map != []:
self.custom_map = self.check_obstacle(self.custom_map)
self._matrix.insert(0, self.generate_custom_map(self.custom_map))
else:
self._matrix.insert(0, self._generate_row())

def state(self):
"""Return read only serialize-able state for sending to client"""
Expand Down Expand Up @@ -80,3 +90,24 @@ def _generate_row(self):
row[cell + lane * config.cells_per_player] = obstacle

return row

def check_obstacle(self, custom_map):
for row in range(len(custom_map)-1):
for col in range(len(custom_map[row])-1):
if custom_map[row][col] not in obstacles.ALL:
print(custom_map[row][col])
custom_map[row][col] = obstacles.get_random_obstacle()
return custom_map

def generate_custom_map(self,custom_map):
if self.custom_index >= len(custom_map):
self.custom_index = 0

row = custom_map[self.custom_index]
self.custom_index += 1

return [
getattr(obstacles, value.upper(), obstacles.NONE) if value else obstacles.NONE
for value in row
]

Loading