Skip to content

Commit

Permalink
Fixing clickable bug fixes and some slight polish
Browse files Browse the repository at this point in the history
- two armies after attack no longer prompts user
- no longer able to select enemy territory in fortify
- origin fortify territory forced highlight
- clickables now wait for mouse release confirmation
- clickables now triggered by mouse click
- now allowing cancel action in attack and fortify DFA
  • Loading branch information
frgorp committed Apr 1, 2013
1 parent 4aef651 commit a4bcc76
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 47 deletions.
Binary file modified resources/risk_board.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 9 additions & 2 deletions risk/graphics/assets/clickable.py
Expand Up @@ -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):
Expand Down Expand Up @@ -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()
Expand All @@ -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

Expand Down
18 changes: 8 additions & 10 deletions risk/graphics/assets/dialog.py
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
18 changes: 18 additions & 0 deletions risk/graphics/event.py
Expand Up @@ -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
Expand Down Expand Up @@ -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)

3 changes: 2 additions & 1 deletion risk/graphics/graphics.py
Expand Up @@ -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
99 changes: 65 additions & 34 deletions risk/graphics/input.py
Expand Up @@ -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 *
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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:
Expand All @@ -151,38 +155,51 @@ 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
picasso = get_picasso()
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()
dialog = BlockingSliderDialogAsset(DIALOG_X, DIALOG_Y, 'Attack Move', 1,
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:
Expand All @@ -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):
Expand All @@ -208,47 +225,61 @@ 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()
dialog = BlockingSliderDialogAsset(DIALOG_X, DIALOG_Y, 'Fortify', 0,
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)
game_master.player_move_armies(player, origin.name,
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..."
Expand Down

0 comments on commit a4bcc76

Please sign in to comment.