''' Created on Jun 16, 2016 @author: Brett Paufler Copyright Brett Paufler Random Play finished 6-17-16 (+/- 2 days) Logic finished on 7-7-16 (+/- 3 weeks) Tk Graphical Implementation of Risk It Will Crash if No Human Player (maximum recursion exceeded) See Risk It Logic for Rules Essentially, try to take over the board, attacking horizontally or vertically click square control to attack from (or click again to unselect) click square do not control to attack Colored enemies surrender when they are 'significantly' outgunned so these are crucial turning points in the game If the yellow player is controlled by the computer and the grey square is depressed yellow player uses advanced logic I didn't bother to refactor this a final time prior to posting, couldn't be bothered time to move on ''' from Tkinter import Frame, Tk, Button, Label, Checkbutton, IntVar from Tkconstants import N, S, E, W from random import randint, choice, shuffle from itertools import product, cycle from risk_it_logic import Logic import numpy as np class RiskIt(Frame): battle_square_one = None #attack from battle_square_two = None #attack to color_defender = None color_attacker = None player_colors = ['pink', 'cyan', 'yellow', 'green'] players_still_playing = player_colors[:] def __init__(self, master): Frame.__init__(self, master, name='risk_it') self.grid(row=0, column=0, sticky=N+S+E+W) self.windows_background_color = self.cget('bg') self.create_all_widgets() def create_all_widgets(self): self.initialize_is_human_dictionary() self.configure_stretchable() self.create_color_bar_label() self.create_new_game_button() self.create_is_human_buttons() self.create_logic_button() self.create_game_square_buttons() self.start_new_game() def initialize_is_human_dictionary(self): '''These values will change when a is_human_button is depressed.''' self.is_human = { 'pink': IntVar(value=1), 'cyan': IntVar(value=0), 'yellow': IntVar(value=0), 'green': IntVar(value=0), } def configure_stretchable(self): '''Boilerplate to make game widgets stretch with window.''' for row in range(0, 10): self.rowconfigure(row, weight=1) for col in range(0, 8): self.columnconfigure(col, weight=1) def create_color_bar_label(self): self.COLOR_BAR = Label(self) self.COLOR_BAR.grid(row=1, column=0, columnspan=8, sticky=N+S+E+W) def create_new_game_button(self): button = Button(self, name='new', command=self.start_new_game, text='New Game') button.grid(row=0, column=5, columnspan=3, sticky=N+S+E+W) def create_is_human_buttons(self): for i, color in enumerate(self.player_colors): button = Checkbutton(self, name=color, indicatoron=0, background=color, selectcolor=color, borderwidth=5, variable=self.is_human[color]) button.grid(row=0, column=i, sticky=N+S+E+W) def create_logic_button(self): self.yellow_uses_logic = IntVar(value=0) button = Checkbutton(self, name='logic', indicatoron=0, borderwidth=5, variable=self.yellow_uses_logic) button.grid(row=0, column=4, sticky=N+S+E+W) def create_game_square_buttons(self): for x, y in product(range(1, 9), range(1, 9)): button = Button(self, name='%d%d' % (x, y)) button.grid(row=(x+1), column=(y-1), sticky=N+S+E+W) button.bind('', self.on_press_game_square_button) button.num_units = 0 def start_new_game(self): self.COLOR_BAR['text'] = '' self.players_still_playing = self.player_colors[:] self.reset_turn_cycler(self.player_colors) self.reset_battle_squares() self.reset_game_square_buttons() self.reset_is_human_buttons() self.initialize_starting_positions_color() self.initialize_starting_positions_units() self.turn_mechanics_start() def reset_turn_cycler(self, players_list): self.turn_num = cycle(players_list) def reset_battle_squares(self): self.battle_square_one = None self.battle_square_two = None def reset_game_square_buttons(self): for button in self.all_game_buttons(): button['bg'] = self.windows_background_color button['fg'] = 'black' button.num_units = 0 button['text'] = '0' def reset_is_human_buttons(self): for color in self.player_colors: button = self.nametowidget('.risk_it.' + color) button['bg'] = color def initialize_starting_positions_color(self): self.children['33']['bg'] = 'pink' self.children['36']['bg'] = 'cyan' self.children['63']['bg'] = 'green' self.children['66']['bg'] = 'yellow' def initialize_starting_positions_units(self): for i in [33, 36, 63, 66]: self.children[str(i)].num_units = 1 self.children[str(i)]['text'] = str(1) def turn_mechanics_start(self): self.current_players_color = next(self.turn_num) if self.current_players_color in self.players_still_playing: self.COLOR_BAR['bg'] = self.current_players_color self.reset_battle_squares() self.add_units() self.autoplay_check() else: #skip to next player self.turn_mechanics_start() def add_units(self): for button in self.all_game_buttons_of_color(self.current_players_color): button.num_units += 1 button['text'] = button.num_units def autoplay_check(self): '''If any of the four colored buttons are depressed, computer moves, If all four are depressed, recursion overload occurs If yellow is played by computer and final grey square is depressed risk_it_logic is utilized to determine moves.''' if not self.is_human[self.current_players_color].get(): if (self.current_players_color == 'yellow' and self.yellow_uses_logic.get()): self.autoplay_logic() else: self.autoplay_random() def autoplay_logic(self): '''Links to risk_it_logic to make the attack. Note: risk_it buttons on a 1-8x1-8 grid, logic uses 0-7x0-7, which is why all the +/- 1's''' logic = Logic(['neutral', 'random', 'random', 'player', 'random']) logic.units = self.buttons_to_numpy() logic.current_player = 3 logic.attacks_possible_update_scoreless() logic.attacks_possible_score_all() logic.attacks_possible_sort() attack = logic.attacks_possible[0] self.battle_square_one = self.nametowidget( '.risk_it.%d%d' % (attack.row_att + 1, attack.col_att + 1)) self.battle_square_two = self.nametowidget( '.risk_it.%d%d' % (attack.row_def + 1, attack.col_def + 1)) self.battle_mechanics() def buttons_to_numpy(self): board = np.zeros((8,8,5), dtype=np.int8) board[:, :, 0] = 1 for player, color in enumerate(self.player_colors, start=1): buttons = self.all_game_buttons_of_color(color) for button in buttons: name = str(button).split('.') row, col = int(name[2][0]) - 1, int(name[2][1]) - 1 board[row, col, player] = int(button['text']) board[row, col, 0] = 0 return board def autoplay_random(self): '''Random select computer player.''' while not self.battle_square_one: all_players_squares = self.all_game_buttons_of_color(self.current_players_color) possible_offense = choice(all_players_squares) neighbor_squares = self.all_surrounding_buttons(possible_offense) shuffle(neighbor_squares) possible_defense = neighbor_squares.pop() if possible_defense['bg'] != self.current_players_color: self.battle_square_one = possible_offense self.battle_square_two = possible_defense self.battle_mechanics() def all_game_buttons(self): '''Returns a list of all the game buttons.''' all_buttons = [self.nametowidget('%d%d' % (x, y)) for x, y in product(range(1, 9), range(1, 9))] return all_buttons def all_game_buttons_of_color(self, color): all_current_buttons = [button for button in self.all_game_buttons() if button['bg'] == color] return all_current_buttons def all_surrounding_buttons(self, button): '''Like neighbors in logic.''' num = int(button._name) numbers = [num + 1, num -1, num + 10, num -10] numbers = [num for num in numbers if num > 10 and num < 90 and num%10 != 0 and num%10 !=9] squares = [self.nametowidget('.risk_it.' + str(num)) for num in numbers] return squares def on_press_game_square_button(self, event): if self.battle_square_one: if self.battle_square_one == event.widget: self.battle_square_one['fg'] = 'black' self.battle_square_one = None elif self.is_eligle_target(event): self.battle_square_two = event.widget self.battle_mechanics() else: self.play_first_battle_square(event.widget) def is_eligle_target(self, event): different_color = event.widget['bg'] != self.battle_square_one['bg'] next_to_attacker = event.widget in self.all_surrounding_buttons(self.battle_square_one) eligible = different_color and next_to_attacker return eligible def play_first_battle_square(self, button): if button['bg'] == self.current_players_color: button['fg'] = 'red' self.battle_square_one = button def battle_mechanics(self): self.battle_colors_set() self.battle() self.check_for_surrender() self.check_for_win() self.battle_colors_reset_to_none() self.turn_mechanics_start() def battle_colors_set(self): self.color_attacker = self.battle_square_one['bg'] self.color_defender = self.battle_square_two['bg'] def battle_colors_reset_to_none(self): self.color_attacker = None self.color_defender = None def roll_dice_random(self, num): rolls = [randint(0, 9) for _ in range(num)] if 0 in rolls: value = 0 else: value = sum(rolls) return value def battle(self): player_one_score = self.roll_dice_random(self.battle_square_one.num_units) player_two_score = self.roll_dice_random(self.battle_square_two.num_units) if player_one_score > player_two_score: self.color_defender = self.battle_square_two['bg'] self.battle_square_two['bg'] = self.current_players_color self.battle_square_two.num_units = 1 #No Matter What self.battle_square_one.num_units = 1 self.battle_square_one['fg'] = 'black' self.battle_square_one['text'] = self.battle_square_one.num_units self.battle_square_two['text'] = self.battle_square_two.num_units def check_for_surrender(self): '''If defender looses a battle to an attacker that has significantly more units and real estate (* players remaining in the game more), attacker takes over all of losers real estate.''' #Cannot take over the neutral game squares if self.color_defender == self.windows_background_color: return num_players = len(self.players_still_playing) offense_squares = self.all_game_buttons_of_color(self.color_attacker ) defense_squares = self.all_game_buttons_of_color(self.color_defender) offense_num_squares = len(offense_squares) defense_num_squares = len(defense_squares) offense_total_units = sum([square.num_units for square in offense_squares]) defense_total_units = sum([square.num_units for square in defense_squares]) #If attacker significantly out-matches defender (by units and squares) if (offense_num_squares >= num_players * defense_num_squares and offense_total_units >= num_players * defense_total_units): for square in defense_squares: square['bg'] = self.color_attacker is_human_button = self.nametowidget('.risk_it.' + self.color_defender) is_human_button['bg'] = 'black' self.players_still_playing.remove(self.color_defender) def check_for_win(self): if 1 == len(self.players_still_playing): self.COLOR_BAR['text'] = '!!!WINNER!!!' def create_root(): root = Tk() root.wm_title('Risk It!') root.geometry('300x360+500+300') root.iconbitmap(default='p_net.ico') root.columnconfigure(0, weight=1) root.rowconfigure(0, weight=1) return root def launch_risk_it(): '''Essentially main, runs the game.''' root = create_root() risk_it = RiskIt(master=root) risk_it.mainloop() if __name__ == '__main__': launch_risk_it()