diff --git a/.gitignore b/.gitignore index 1b72870..8d0b598 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,9 @@ __pycache__/ *.pyc .coverage htmlcov/ + +.venv/ +venv/ +venv38/ + +.idea diff --git a/README.md b/README.md index 5c9f0c4..3246758 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,11 @@ If you spot a bug or have an idea for enhancement, write us an GitHub issue. ### Requirements -Python v. 3.7 +Python v. 3.8 We recommend to use virtual environment. +(Python version was raised from 3.7 to 3.8 for Pyglet 2) + To successfully run the project, run the script below. It will install all the requirements including additional libraries: - [pyglet](https://bitbucket.org/pyglet/pyglet/wiki/Home) - the graphical library, - [asyncio](https://docs.python.org/3/library/asyncio.html) and [aiohttp](https://aiohttp.readthedocs.io/en/stable/) - asynchronous frameworks to run server and clients, @@ -47,7 +49,7 @@ You can choose a map to play directly from command line by writing the location The same way you can enter the number of players `-p, --players`. The current maps are prepared for the max. 8 players. ``` -python server.py -m maps/game_1.json -p 6 +python server.py -m maps/belt_map.json -p 6 ``` If you run server on a different computer than the clients, get the server's hostname and run clients with its value as the named argument `-h, --hostname`. diff --git a/client_interface.py b/client_interface.py index 71d4777..cbc05f6 100644 --- a/client_interface.py +++ b/client_interface.py @@ -6,6 +6,8 @@ import asyncio import aiohttp import pyglet +pyglet.options['shadow_window'] = False + import click from time import monotonic @@ -156,6 +158,8 @@ def run_from_welcome_board(robot_name, own_robot_name, hostname): def main(hostname, robot_name): interface = Interface(hostname) pyglet.clock.schedule_interval(tick_asyncio, 1/30) + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) asyncio.ensure_future(interface.get_messages(robot_name)) pyglet.app.run() diff --git a/client_receiver.py b/client_receiver.py index 316750c..457e52f 100644 --- a/client_receiver.py +++ b/client_receiver.py @@ -4,6 +4,8 @@ import asyncio import aiohttp import pyglet +pyglet.options['shadow_window'] = False + import click from time import monotonic from util_network import tick_asyncio @@ -107,6 +109,8 @@ def main(hostname): # Schedule the "client" task # More about Futures - official documentation # https://docs.python.org/3/library/asyncio-future.html + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) asyncio.ensure_future(receiver.get_game_state()) pyglet.app.run() diff --git a/client_welcome_board.py b/client_welcome_board.py index 17301f7..6b5c2a2 100644 --- a/client_welcome_board.py +++ b/client_welcome_board.py @@ -8,6 +8,8 @@ import asyncio import aiohttp import pyglet +pyglet.options['shadow_window'] = False + import click from backend import State @@ -97,6 +99,8 @@ def main(hostname): # Schedule the "client" task # More about Futures - official documentation # https://docs.python.org/3/library/asyncio-future.html + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) asyncio.ensure_future(welcome_board.process_message()) pyglet.app.run() diff --git a/export_img.py b/export_img.py index 666b374..5c18a13 100644 --- a/export_img.py +++ b/export_img.py @@ -6,7 +6,7 @@ import subprocess from pathlib import Path -inkscape_paths = [ +INKSCAPE_PATHS = [ "inkscape", "C:/Program Files/Inkscape/inkscape", "C:/Program Files (x86)/Inkscape/inkscape" @@ -29,11 +29,11 @@ def find_inkscape_path(): """ Return first functional Inkscape path """ - for path in inkscape_paths: + for path in INKSCAPE_PATHS: if run_inkscape(path): return path -inkscape = find_inkscape_path() +INKSCAPE = find_inkscape_path() def export_svg_png(): """ @@ -56,7 +56,9 @@ def export_svg_png(): new_name.parent.mkdir(exist_ok=True, parents=True) # launch Inkscape and export all images to PNG format # More info about subprocess in official documentation - https://docs.python.org/3/library/subprocess.html - subprocess.run([inkscape, name, "--export-png=" + str(new_name), "--export-area-page"], check=True) + print(name) + subprocess.run([INKSCAPE, name, "--export-filename="+str(new_name), "--export-area-page"], check=True) -export_svg_png() -print("Done") +if __name__ == '__main__': + export_svg_png() + print("Done") diff --git a/frontend.py b/frontend.py index 09bef78..37dac95 100644 --- a/frontend.py +++ b/frontend.py @@ -5,36 +5,61 @@ """ import pyglet +pyglet.options['shadow_window'] = False + +from pyglet.gl import Config as GLConfig from pathlib import Path from time import monotonic -from util_frontend import TILE_WIDTH, TILE_HEIGHT, get_label, get_sprite, window_zoom +from util_frontend import (TILE_WIDTH, TILE_HEIGHT, get_label, get_sprite, window_zoom, + _init_module_after_gl_context as _util_frontend_init_module_after_gl_context) import math -# Loading of tiles and robots images -loaded_tiles_images = {} -for image_path in Path('./img/tiles/png').iterdir(): - loaded_tiles_images[image_path.stem] = pyglet.image.load(image_path) -loaded_robots_images = {} -for image_path in Path('./img/robots_map/png').iterdir(): - loaded_robots_images[image_path.stem] = pyglet.image.load(image_path) +loaded_tiles_images = {} # init inside _init_module_after_gl_context +loaded_robots_images = {} # init inside _init_module_after_gl_context + +_init_module_after_gl_context_called = False + +def _init_module_after_gl_context(): + """ Loading of tiles and robots images + because of the gl.gl_compat functions we need to load sprites + after a window with correct GL context is created + """ + global loaded_tiles_images, loaded_robots_images + global border_sprite, winner_sprite #, game_over_sprite + global _init_module_after_gl_context_called + if _init_module_after_gl_context_called: + return + _init_module_after_gl_context_called = True -# Border of available robot's picture -border_sprite = get_sprite('./img/interface/png/border.png') -# Winner -winner_sprite = get_sprite('img/interface/png/game_winner.png', x=170, y=200) -# Game over -# game_over_sprite = get_sprite('img/interface/png/game_over.png', x=140, y=180) + for image_path in Path('./img/tiles/png').iterdir(): + loaded_tiles_images[image_path.stem] = pyglet.image.load(str(image_path)) + + for image_path in Path('./img/robots_map/png').iterdir(): + loaded_robots_images[image_path.stem] = pyglet.image.load(str(image_path)) + + # Border of available robot's picture + border_sprite = get_sprite('./img/interface/png/border.png') + # Winner + winner_sprite = get_sprite('img/interface/png/game_winner.png', x=170, y=200) + # Game over + # game_over_sprite = get_sprite('img/interface/png/game_over.png', x=140, y=180) def create_window(state, on_draw): """ Return a pyglet window for graphic output. + Loads global sprites in the first call after the window is created with compatible GL context. state: State object containing game board, robots and map sizes """ + gl_config = GLConfig(major_version=2, minor_version=1, forward_compatible=False) window = pyglet.window.Window(state.tile_count[0] * TILE_WIDTH, - state.tile_count[1] * TILE_HEIGHT + 50, resizable=True) + state.tile_count[1] * TILE_HEIGHT + 50, + config=gl_config, resizable=True) + _util_frontend_init_module_after_gl_context() + _init_module_after_gl_context() + window.push_handlers(on_draw=on_draw) return window diff --git a/game.py b/game.py index e4c0670..b639432 100644 --- a/game.py +++ b/game.py @@ -5,6 +5,8 @@ - choose standard or other map to be loaded """ import pyglet +pyglet.options['shadow_window'] = False + import sys from backend import State diff --git a/interface_frontend.py b/interface_frontend.py index fea3b99..802a33c 100644 --- a/interface_frontend.py +++ b/interface_frontend.py @@ -1,8 +1,12 @@ import pyglet +pyglet.options['shadow_window'] = False + +from pyglet.gl import Config as GLConfig from time import monotonic from util_frontend import TILE_WIDTH, TILE_HEIGHT, get_label, get_sprite -from util_frontend import window_zoom, loaded_robots_images, player_sprite +from util_frontend import (window_zoom, loaded_robots_images, player_sprite_proxy, + _init_module_after_gl_context as _util_frontend_init_module_after_gl_context) MAX_LIVES_COUNT = 3 MAX_FLAGS_COUNT = 8 @@ -17,7 +21,13 @@ def create_window(on_draw, on_text, on_mouse_press, on_close): """ Return a pyglet window for graphic output. """ - window = pyglet.window.Window(WINDOW_WIDTH, WINDOW_HEIGHT, resizable=True) + gl_config = GLConfig(major_version=2, minor_version=1, forward_compatible=False) + window = pyglet.window.Window(WINDOW_WIDTH, WINDOW_HEIGHT, + config=gl_config, resizable=True) + + _util_frontend_init_module_after_gl_context() + _init_module_after_gl_context() + window.push_handlers( on_draw=on_draw, on_text=on_text, @@ -26,92 +36,108 @@ def create_window(on_draw, on_text, on_mouse_press, on_close): ) return window - -# Interface element sprites -# Interface background -interface_sprite = get_sprite('img/interface/png/interface.png', x=0, y=0) -power_down_sprite = get_sprite('img/interface/png/power.png', x=210, y=900) -power_down_player_sprite = get_sprite('img/interface/png/power_player.png') -# Winner crown -crown_sprite = get_sprite('img/interface/png/crown.png') -# Loss crown -loss_sprite = get_sprite('img/interface/png/no_crown.png') -# Time indicator -indicator_green_sprite = get_sprite('img/interface/png/green.png', x=688, y=864) -# Time indicator -indicator_red_sprite = get_sprite('img/interface/png/red.png', x=688, y=864) -# Universal cards background -card_background_sprite = get_sprite('img/interface/png/card_bg.png') -# Own robot's border -own_border_sprite = get_sprite('img/interface/png/own_border.png', x=33, y=42) -# Gray overlay on selected cards -select_sprite = get_sprite('img/interface/png/card_cv.png') -# Selection cursor -cursor_sprite = get_sprite('img/interface/png/card_sl.png') -# Winner -you_win_sprite = get_sprite('img/interface/png/winner.png', x=160, y=290) -winner_of_the_game_sprite = get_sprite('img/interface/png/game_winner.png', x=160, y=200) -# Game over -game_over_sprite = get_sprite('img/interface/png/game_over.png', x=140, y=280) -# Other robot card -players_background = get_sprite('img/interface/png/player.png') -# My_robot_sprite, below replaced with the actual image. -my_robot_sprite = get_sprite('img/robots/png/bender.png', x=64, y=882) +_init_module_after_gl_context_called = False + +def _init_module_after_gl_context(): + global interface_sprite, power_down_sprite, power_down_player_sprite + global crown_sprite, loss_sprite, indicator_green_sprite, indicator_red_sprite + global card_background_sprite, own_border_sprite, select_sprite + global cursor_sprite, you_win_sprite, winner_of_the_game_sprite + global game_over_sprite, players_background, my_robot_sprite, flag_slot_sprite + global _init_module_after_gl_context_called + if _init_module_after_gl_context_called: + return + _init_module_after_gl_context_called = True + + # Interface element sprites + # Interface background + interface_sprite = get_sprite('img/interface/png/interface.png', x=0, y=0) + power_down_sprite = get_sprite('img/interface/png/power.png', x=210, y=900) + power_down_player_sprite = get_sprite('img/interface/png/power_player.png') + # Winner crown + crown_sprite = get_sprite('img/interface/png/crown.png') + # Loss crown + loss_sprite = get_sprite('img/interface/png/no_crown.png') + # Time indicator + indicator_green_sprite = get_sprite('img/interface/png/green.png', x=688, y=864) + # Time indicator + indicator_red_sprite = get_sprite('img/interface/png/red.png', x=688, y=864) + # Universal cards background + card_background_sprite = get_sprite('img/interface/png/card_bg.png') + # Own robot's border + own_border_sprite = get_sprite('img/interface/png/own_border.png', x=33, y=42) + # Gray overlay on selected cards + select_sprite = get_sprite('img/interface/png/card_cv.png') + # Selection cursor + cursor_sprite = get_sprite('img/interface/png/card_sl.png') + # Winner + you_win_sprite = get_sprite('img/interface/png/winner.png', x=160, y=290) + winner_of_the_game_sprite = get_sprite('img/interface/png/game_winner.png', x=160, y=200) + # Game over + game_over_sprite = get_sprite('img/interface/png/game_over.png', x=140, y=280) + # Other robot card + players_background = get_sprite('img/interface/png/player.png') + # My_robot_sprite, below replaced with the actual image. + my_robot_sprite = get_sprite('img/robots/png/bender.png', x=64, y=882) + flag_slot_sprite = get_sprite('img/interface/png/flag_slot.png') + + for i in range(MAX_LIVES_COUNT): + x = 354 + i * 46 + y = 864 + lives_sprites.append(get_sprite('img/interface/png/life.png', x, y)) + for i in range(MAX_FLAGS_COUNT): + x = 332 + i * 48 + y = 928 + flags_sprites.append(get_sprite(f'img/tiles/png/flag_{i+1}.png', x, y)) + + for i in range(MAX_DAMAGES_COUNT): + x = 677 + i * -70 + y = 773 + damages_tokens_sprites.append(get_sprite('img/interface/png/token.png', x, y)) + + for i in range(MAX_DAMAGES_COUNT): + x = 677 + i * -70 + y = 773 + permanent_damages_sprites.append(get_sprite('img/interface/png/permanent_damage.png', x, y)) + + cards_type_sprites.update({ + 'u_turn': get_sprite('img/interface/png/u_turn.png'), + 'back_up': get_sprite('img/interface/png/back.png'), + 'left': get_sprite('img/interface/png/rotate_left.png'), + 'right': get_sprite('img/interface/png/rotate_right.png'), + 'move1': get_sprite('img/interface/png/move1.png'), + 'move2': get_sprite('img/interface/png/move2.png'), + 'move3': get_sprite('img/interface/png/move3.png'), + }) + + for i in range(5): + x, y = 47, 384 + # 144 space between cards + x = x + i * 144 + dealt_cards_coordinates.append((x, y)) + for i in range(4): + x, y = 120, 224 + x = x + i * 144 + dealt_cards_coordinates.append((x, y)) + for i in range(5): + x, y = 47, 576 + x = x + i * 144 + program_coordinates.append((x, y)) lives_sprites = [] -for i in range(MAX_LIVES_COUNT): - x = 354 + i * 46 - y = 864 - lives_sprites.append(get_sprite('img/interface/png/life.png', x, y)) - flags_sprites = [] -for i in range(MAX_FLAGS_COUNT): - x = 332 + i * 48 - y = 928 - flags_sprites.append(get_sprite(f'img/tiles/png/flag_{i+1}.png', x, y)) - -flag_slot_sprite = get_sprite('img/interface/png/flag_slot.png') # Tokens of damage damages_tokens_sprites = [] -for i in range(MAX_DAMAGES_COUNT): - x = 677 + i * -70 - y = 773 - damages_tokens_sprites.append(get_sprite('img/interface/png/token.png', x, y)) permanent_damages_sprites = [] -for i in range(MAX_DAMAGES_COUNT): - x = 677 + i * -70 - y = 773 - permanent_damages_sprites.append(get_sprite('img/interface/png/permanent_damage.png', x, y)) # Cards sprites -cards_type_sprites = { - 'u_turn': get_sprite('img/interface/png/u_turn.png'), - 'back_up': get_sprite('img/interface/png/back.png'), - 'left': get_sprite('img/interface/png/rotate_left.png'), - 'right': get_sprite('img/interface/png/rotate_right.png'), - 'move1': get_sprite('img/interface/png/move1.png'), - 'move2': get_sprite('img/interface/png/move2.png'), - 'move3': get_sprite('img/interface/png/move3.png'), -} +cards_type_sprites = {} dealt_cards_coordinates = [] -for i in range(5): - x, y = 47, 384 - # 144 space between cards - x = x + i * 144 - dealt_cards_coordinates.append((x, y)) -for i in range(4): - x, y = 120, 224 - x = x + i * 144 - dealt_cards_coordinates.append((x, y)) program_coordinates = [] -for i in range(5): - x, y = 47, 576 - x = x + i * 144 - program_coordinates.append((x, y)) # dict for drawing cards names cards_type_names = { @@ -341,10 +367,10 @@ def draw_robot(i, robot, game_state): # Robot´s image if robot.name in loaded_robots_images: - player_sprite.image = loaded_robots_images[robot.name] - player_sprite.x = 139 + i * GAP - player_sprite.y = 90 - player_sprite.draw() + player_sprite_proxy.image = loaded_robots_images[robot.name] + player_sprite_proxy.x = 139 + i * GAP + player_sprite_proxy.y = 90 + player_sprite_proxy.draw() # Robot's name robot_name_label = get_label( diff --git a/loading.py b/loading.py index 61ff71e..be1b2e5 100644 --- a/loading.py +++ b/loading.py @@ -58,9 +58,7 @@ def get_tiles_data(map_data): except KeyError: loaded_tileset = map_data['tilesets'][0]['tiles'] - # Whatever happened above, return the set variable for further processing. - finally: - return loaded_tileset + return loaded_tileset def get_tiles_properties(map_data): diff --git a/util_frontend.py b/util_frontend.py index 06467fa..6adbb7f 100644 --- a/util_frontend.py +++ b/util_frontend.py @@ -3,7 +3,6 @@ from functools import lru_cache from pathlib import Path - # Constatnts for size of tile image in px TILE_WIDTH = 64 TILE_HEIGHT = 64 @@ -39,21 +38,54 @@ def window_zoom(window, WINDOW_WIDTH, WINDOW_HEIGHT): """ Contextmanager for zoom of window. """ - pyglet.gl.glPushMatrix() + pyglet.gl.gl_compat.glPushMatrix() window.clear() zoom = min( window.height / WINDOW_HEIGHT, window.width / WINDOW_WIDTH ) - pyglet.gl.glScalef(zoom, zoom, 1) + pyglet.gl.gl_compat.glScalef(zoom, zoom, 1) yield - pyglet.gl.glPopMatrix() + pyglet.gl.gl_compat.glPopMatrix() + + +_init_module_after_gl_context_called = False + +def _init_module_after_gl_context(): + """ loads sprites into global variables after a GL context is created. """ + global _init_module_after_gl_context_called + if _init_module_after_gl_context_called: + return + _init_module_after_gl_context_called = True + + # Loading of robots images + for image_path in Path('./img/robots/png').iterdir(): + loaded_robots_images[image_path.stem] = pyglet.image.load(str(image_path)) + + # Create player sprite, use fake image, replaced with the actual one- + player_sprite_proxy._set_actual_sprite(get_sprite('img/robots/png/bender.png')) + +loaded_robots_images = {} # initialized in init_module_after_gl_context + +class PlayerSpriteProxy: + # player_sprite is used from multiple modules in same running program + # and cannot be created as None -> a proxy object + + def __init__(self): + self.__dict__['_actual_sprite'] = None + + def _set_actual_sprite(self, actual_sprite): + self._actual_sprite = actual_sprite + def __getattr__(self, name): + if name in self.__dict__: + return self.__dict__[name] + return getattr(self._actual_sprite, name) -# Loading of robots images -loaded_robots_images = {} -for image_path in Path('./img/robots/png').iterdir(): - loaded_robots_images[image_path.stem] = pyglet.image.load(image_path) + def __setattr__(self, name, value): + if name in self.__dict__: + self.__dict__[name] = value + else: + setattr(self._actual_sprite, name, value) -# Create player sprite, use fake image, replaced with the actual one- -player_sprite = get_sprite('img/robots/png/bender.png') +player_sprite_proxy = PlayerSpriteProxy() diff --git a/welcome_board_frontend.py b/welcome_board_frontend.py index 6b0d5c5..29eeb1a 100644 --- a/welcome_board_frontend.py +++ b/welcome_board_frontend.py @@ -1,37 +1,53 @@ import pyglet - +from pyglet.gl import Config as GLConfig from util_frontend import get_sprite, get_label, window_zoom -from util_frontend import loaded_robots_images, player_sprite +from util_frontend import (loaded_robots_images, player_sprite_proxy, + _init_module_after_gl_context as _util_frontend_init_module_after_gl_context) WINDOW_WIDTH = 1030 WINDOW_HEIGHT = 578 -# Background -background_sprite = get_sprite('img/interface/png/board.png', x=0, y=0) -# Border of available robot's picture -border_sprite = get_sprite('img/interface/png/border.png') -robot_background_sprite = get_sprite('img/interface/png/robot_bcg.png') - picture_coordinates = [] for i in range(8): x = 50 + i * 120 y = 120 picture_coordinates.append((x, y)) -not_available_label = get_label( - "No more available robots.", - x=500, y=50, - font_size=20, anchor_x="center", - color=(255, 0, 0, 255), -) +_init_module_after_gl_context_called = False + +def _init_module_after_gl_context(): + """ loads sprites into global variables after a GL context is created. """ + global background_sprite, border_sprite, robot_background_sprite + global not_available_label + global _init_module_after_gl_context_called + _init_module_after_gl_context_called = True + + # Background + background_sprite = get_sprite('img/interface/png/board.png', x=0, y=0) + # Border of available robot's picture + border_sprite = get_sprite('img/interface/png/border.png') + robot_background_sprite = get_sprite('img/interface/png/robot_bcg.png') + + not_available_label = get_label( + "No more available robots.", + x=500, y=50, + font_size=20, anchor_x="center", + color=(255, 0, 0, 255), + ) def create_window(on_draw, on_mouse_press, on_text, on_text_motion): """ Return a pyglet window for graphic output. """ - window = pyglet.window.Window(WINDOW_WIDTH, WINDOW_HEIGHT, resizable=True) + gl_config = GLConfig(major_version=2, minor_version=1, forward_compatible=False) + window = pyglet.window.Window(WINDOW_WIDTH, WINDOW_HEIGHT, + config=gl_config, resizable=True) + + _init_module_after_gl_context() + _util_frontend_init_module_after_gl_context() + window.push_handlers( on_draw=on_draw, on_mouse_press=on_mouse_press, @@ -56,20 +72,20 @@ def draw_board(state, available_robots, window, own_robot_name): robot_background_sprite.x = x robot_background_sprite.y = y robot_background_sprite.draw() - player_sprite.image = loaded_robots_images[robot.name] - player_sprite.x = x - player_sprite.y = y - player_sprite.draw() + player_sprite_proxy.image = loaded_robots_images[robot.name] + player_sprite_proxy.x = x + player_sprite_proxy.y = y + player_sprite_proxy.draw() robot_name_label = get_label( - str(robot.displayed_name), player_sprite.x + 30, player_sprite.y - 26, + str(robot.displayed_name), player_sprite_proxy.x + 30, player_sprite_proxy.y - 26, 16, "center", (0, 0, 0, 255), ) robot_name_label.draw() for available_robot in available_robots: if available_robot.name == robot.name: - border_sprite.x = player_sprite.x - border_sprite.y = player_sprite.y + border_sprite.x = player_sprite_proxy.x + border_sprite.y = player_sprite_proxy.y border_sprite.draw() if not available_robots: @@ -99,8 +115,8 @@ def handle_click(state, x, y, window, available_robots): for i, coordinate in enumerate(picture_coordinates): coord_x, coord_y = coordinate if ( - coord_x < x < coord_x + player_sprite.width and - coord_y < y < coord_y + player_sprite.height + coord_x < x < coord_x + player_sprite_proxy.width and + coord_y < y < coord_y + player_sprite_proxy.height ): robot_name = sorted(loaded_robots_images.keys())[i] for available_robot in available_robots: