diff --git a/resources/risk_board.png b/resources/risk_board.png index a746d40..408730e 100644 Binary files a/resources/risk_board.png and b/resources/risk_board.png differ diff --git a/risk/graphics/assets/clickable.py b/risk/graphics/assets/clickable.py index b78a19f..eb8e86b 100644 --- a/risk/graphics/assets/clickable.py +++ b/risk/graphics/assets/clickable.py @@ -4,8 +4,10 @@ from pygame.font import Font import risk.graphics.assets +import risk.graphics.event from risk.graphics.assets import base from risk.graphics.assets.base import PicassoAsset +from risk.graphics.event import wait_for_mouse_release _FONT = 'resources/Vera.ttf' class ClickableAsset(PicassoAsset): @@ -38,8 +40,8 @@ def __init__(self, x, y, width, height, msg, size=16, PicassoAsset.__init__(self, self.normal, x, y) def draw(self): - if not self.disabled and (self.mouse_hovering() or \ - self.force_highlight): + if self.force_highlight or (self.mouse_hovering() and \ + not self.disabled): return self._highlighted_surface() else: return self._normal_surface() @@ -55,6 +57,11 @@ def mouse_hovering(self, mouse_pos=None): self.y + self.offset_y).collidepoint( mouse_pos) and not self.disabled + # take control of main thread until mouse released + def confirmed_click(self): + release = wait_for_mouse_release() + return self.mouse_hovering(release.pos) + def _normal_surface(self): return self.normal diff --git a/risk/graphics/assets/dialog.py b/risk/graphics/assets/dialog.py index dd5ffd5..05abda2 100644 --- a/risk/graphics/assets/dialog.py +++ b/risk/graphics/assets/dialog.py @@ -5,7 +5,7 @@ import risk.graphics.assets from risk.graphics.picasso import get_picasso -from risk.graphics.event import wait_for_event +from risk.graphics.event import wait_for_event, wait_for_mouse_click from risk.graphics.event import pump from risk.graphics.assets.base import BLACK, BROWN, WHITE from risk.graphics.assets.base import PicassoAsset @@ -168,15 +168,13 @@ def draw(self): def get_result(self, poll_sleep): done = False while not done: - event = wait_for_event() - if event.type == pygame.MOUSEBUTTONDOWN: - if self.slider.mouse_hovering(event.pos): - self.drag_slider(poll_sleep) - elif self.being_dragged(event.pos): - self.drag_dialog() - elif event.type == pygame.MOUSEBUTTONUP and \ - self.finished_button.mouse_hovering(event.pos): - done = True + event = wait_for_mouse_click() + if self.slider.mouse_hovering(event.pos): + self.drag_slider(poll_sleep) + elif self.being_dragged(event.pos): + self.drag_dialog() + elif self.finished_button.mouse_hovering(event.pos): + done = self.finished_button.confirmed_click() return self.current def reset(self): diff --git a/risk/graphics/event.py b/risk/graphics/event.py index 1116cd4..6e1e7d4 100644 --- a/risk/graphics/event.py +++ b/risk/graphics/event.py @@ -18,6 +18,9 @@ _NO_BLOCK = False mutex = threading.Semaphore() +############################################################################### +## Critical section functions +# def wait_for_event(): # this is a bit strange, but pygame will invoke pump internally so there's # really no reason to call pump when waiting for event @@ -46,3 +49,18 @@ def pump(): pygame.event.pump() mutex.release() +############################################################################### +## Safe functions that don't touch CS +# +def wait_for_event_type(event_type): + event = wait_for_event() + while event.type != event_type: + event = wait_for_event() + return event + +def wait_for_mouse_release(): + return wait_for_event_type(pygame.MOUSEBUTTONUP) + +def wait_for_mouse_click(): + return wait_for_event_type(pygame.MOUSEBUTTONDOWN) + diff --git a/risk/graphics/graphics.py b/risk/graphics/graphics.py index 97fa331..b950bca 100644 --- a/risk/graphics/graphics.py +++ b/risk/graphics/graphics.py @@ -196,5 +196,6 @@ def pressed_clickables(mouse_pos, storage='buttons'): clicked = [] for name, button in storage.iteritems(): if button.mouse_hovering(mouse_pos): - clicked.append((name, button)) + if button.confirmed_click(): + clicked.append((name, button)) return clicked diff --git a/risk/graphics/input.py b/risk/graphics/input.py index 9474f9c..bec2b30 100644 --- a/risk/graphics/input.py +++ b/risk/graphics/input.py @@ -17,6 +17,7 @@ from risk.errors.battle import RiskBattleError from risk.graphics.event import wait_for_event, get_events +from risk.graphics.event import wait_for_mouse_click from risk.graphics.datastore import Datastore from risk.graphics.picasso import get_picasso from risk.graphics.assets.player import * @@ -53,18 +54,18 @@ def get_clicked_buttons(mouse_pos): def handle_user_mouse_input(game_master, state_entry): player = game_master.current_player() state_entry(player, game_master) - #scan_pygame_mouse_event(player, game_master, state_entry) + #wait_for_mouse_click(player, game_master, state_entry) def get_all_clickables(): datastore = Datastore() return datastore.get_storage('buttons').values() + \ datastore.get_storage('territories').values() -def disable_clickables(): +def disable_all_clickables(): for clickable in get_all_clickables(): clickable.disabled = True -def enable_clickables(): +def enable_all_clickables(): for clickable in get_all_clickables(): clickable.disabled = False @@ -81,21 +82,24 @@ def enable_adjacent_territories(origin): for neighbour in origin.neighbours.values(): assets[neighbour.name].disabled = False -def scan_pygame_mouse_event(): - _WAITING_FOR_INPUT = True - - while _WAITING_FOR_INPUT: - event = wait_for_event() - if event.type == pygame.MOUSEBUTTONUP: - return event +#def wait_for_mouse_click(): +# _WAITING_FOR_INPUT = True +# +# while _WAITING_FOR_INPUT: +# event = wait_for_event() +# if event.type == pygame.MOUSEBUTTONDOWN: +# return event -def wait_for_territory_click(): +def wait_for_territory_click(allow_fail=False): _NO_TERRITORY_CLICKED = True while _NO_TERRITORY_CLICKED: - event = scan_pygame_mouse_event() + event = wait_for_mouse_click() for name, clickable in get_clicked_territories(event.pos): if isinstance(clickable, TerritoryAsset): return clickable + # short circuit condition + if allow_fail: + return None ############################################################################### ## Reinforce phase DFA @@ -110,7 +114,7 @@ def reinforce_phase(player, game_master): # core state machine disable_enemy_territories(player) while player.reserves > 0: - event = scan_pygame_mouse_event() + event = wait_for_mouse_click() for name, clickable in graphics.pressed_clickables(event.pos, 'territories'): if isinstance(clickable, TerritoryAsset): @@ -121,7 +125,7 @@ def reinforce_phase(player, game_master): except GameMasterError: reinforce_add_army_fail(player, game_master, territory) # exit state - enable_clickables() + enable_all_clickables() picasso.remove_asset(LAYER, reserve_count_asset) def reinforce_add_army(player, game_master, territory, number_of_armies=1): @@ -142,7 +146,7 @@ def attack_phase(player, game_master): done = False while not done: disable_enemy_territories(player) - event = scan_pygame_mouse_event() + event = wait_for_mouse_click() for name, clickable in get_clicked_territories(event.pos): if isinstance(clickable, TerritoryAsset): if clickable.territory.owner == player: @@ -151,7 +155,7 @@ def attack_phase(player, game_master): for name, clickable in get_clicked_buttons(event.pos): if name == 'next': done = True - enable_clickables() + enable_all_clickables() def attack_choose_target(player, game_master, origin_asset): origin = origin_asset.territory @@ -159,22 +163,35 @@ def attack_choose_target(player, game_master, origin_asset): datastore = Datastore() feedback_asset = datastore.get_entry('attack_feedback') picasso.add_asset(LAYER, feedback_asset) - disable_clickables() + disable_all_clickables() origin_asset.force_highlight = True enable_adjacent_territories(origin) - target = wait_for_territory_click().territory + target_asset = wait_for_territory_click(allow_fail=True) + target = None + if target_asset: + target = target_asset.territory + if not target: + pass + else: + attack_perform_attack(player, game_master, origin, target) + picasso.remove_asset(LAYER, feedback_asset) + origin_asset.force_highlight = False + enable_all_clickables() + +def attack_perform_attack(player, game_master, origin, target): try: success = game_master.player_attack(player, origin.name, target.name) if success: - attack_success_move_armies(player, game_master, origin, target) + if origin.armies > 2: + attack_success_move_armies(player, game_master, origin, target) + else: + game_master.player_move_armies(player, origin.name, + target.name, 1) else: attack_failed(player, game_master, origin, target) except (GameMasterError, RiskBattleError, KeyError): pass - finally: - picasso.remove_asset(LAYER, feedback_asset) - origin_asset.force_highlight = False - enable_clickables() + def attack_success_move_armies(player, game_master, origin, target): picasso = get_picasso() @@ -182,7 +199,7 @@ def attack_success_move_armies(player, game_master, origin, target): origin.armies, slider_update, [origin, target]) dialog.add_text(16, 16, "Attack was successful!") dialog.add_text(16, 32, "How many armies to move?") - disable_clickables() + disable_all_clickables() picasso.add_asset(LAYER, dialog) done = False while not done: @@ -197,7 +214,7 @@ def attack_success_move_armies(player, game_master, origin, target): except ValueError: # we really shouldn't get a parsing error from numeric dialog raise - enable_clickables() + enable_all_clickables() picasso.remove_asset(LAYER, dialog) def attack_failed(player, game_master, origin, target): @@ -208,25 +225,38 @@ def attack_failed(player, game_master, origin, target): # def fortify_phase(player, game_master): + disable_enemy_territories(player) done = False # TODO merge with attack phase block while not done: - event = scan_pygame_mouse_event() + event = wait_for_mouse_click() for name, clickable in get_clicked_territories(event.pos): if isinstance(clickable, TerritoryAsset): if clickable.territory.owner == player: - fortify_choose_target(player, game_master, - clickable.territory) + fortify_choose_target(player, game_master, clickable) for name, clickable in get_clicked_buttons(event.pos): if name == 'next': done = True + enable_all_clickables() -def fortify_choose_target(player, game_master, origin): - target = wait_for_territory_click().territory - if target.owner == player and origin.is_connected(target): +def fortify_choose_target(player, game_master, origin_asset): + origin_asset.disabled = True + origin_asset.force_highlight = True + origin = origin_asset.territory + target_asset = wait_for_territory_click(allow_fail=True) + target = None + if target_asset: + target = target_asset.territory + if not target: + # cancel + pass + elif target.owner == player and origin.is_connected(target): fortify_choose_armies_to_move(player, game_master, origin, target) else: - fortify_failed(player, origin, target, NotConnected(None, None)) + fortify_failed(player, origin, target, + TerritoryNotOwnedByPlyer(player)) + origin_asset.force_highlight = False + origin_asset.disabled = False def fortify_choose_armies_to_move(player, game_master, origin, target): picasso = get_picasso() @@ -234,7 +264,7 @@ def fortify_choose_armies_to_move(player, game_master, origin, target): origin.armies, slider_update, [origin, target]) dialog.add_text(16, 16, "Fortifying...") dialog.add_text(16, 35, "Select amount of armies to move") - disable_clickables() + disable_all_clickables() picasso.add_asset(LAYER, dialog) try: number_to_move = dialog.get_result(INPUT_POLL_SLEEP) @@ -242,13 +272,14 @@ def fortify_choose_armies_to_move(player, game_master, origin, target): target.name, number_to_move) except GameMasterError as e: fortify_failed(player, origin, target, e) - enable_clickables() + enable_all_clickables() picasso.remove_asset(LAYER, dialog) def fortify_failed(player, origin, target, reason): picasso = get_picasso() expected = { NotConnected: 'The selected territories are not connected!', + TerritoryNotOwnedByPlayer: "You do not own %s!" % target.name, } msg = expected[reason.__class__] if reason.__class__ in expected.keys() \ else "Unkonwn reason for failure..."