def __init__(self, *pars, **kpars): AnchorLayout.__init__(self, *pars, **kpars) self.cur_dir = dirname(abspath('file')) config = Configuration() config.purgelines() config.initialize() # do not use '-' or other chars in the name! self.username = '' self.turnables = config.getParam('turnable') self.cards_unturnable = bool(int(config.getParam('cards_unturnable'))) # enables the automatic player agent, playing as NK as default. self.rp = None # Rule Parser self.auto_player = bool(int(config.getParam('auto_player'))) if self.auto_player: self.rp = RuleParser() self.rp.load_rules() self.format = config.getParam('format') self.help_file = config.getParam('help_file') self.shoe_file = config.getParam('shoe_file') self.srv = config.getParam('server') self.srv_port = config.getParam('server_port') if self.format == 'circular': self.turnable = dict(zip(TTTGame.CIRCULAR_CARD_LAYOUT, [bool(int(x)) for x in self.turnables.split(',')])) else: self.turnable = dict(zip(TTTGame.LINEAR_CARD_LAYOUT, [bool(int(x)) for x in self.turnables.split(',')])) self.timer_start = int(config.getParam('timer_start')) # load default shoe_file: self.shoe_config = Configuration(config_file=self.shoe_file) self.shoe_config.purgelines() # self.turn = '' # current player turn self.current_target_card = '' # the target card stated in the shoe_file ("2H" usually) print self.shoe_config.content self.hands = [] # store all hands # file names are in the form: output-<dmY>-<HM>.txt # Here we just create a reference. The actual obj is made when the login popup is dismissed self.fout_handle = None self.fout_time_handle = None self.timeHistory = [] # list holding the strings: <move> <time>\n self.stopwatch = StopWatch() self.nk_history_q = deque([], 2) # nk history of the last 2 hands. Required for automatic agent self.ck_history_q = deque([], 2) # ck history of the last 2 hands. Required for automatic agent
class TTTGame(AnchorLayout): bottom_bar = ObjectProperty(None) game_table = ObjectProperty(None) side_bar = ObjectProperty(None) console = ObjectProperty(None) timer = ObjectProperty(None) hand = ListProperty() total_moves = NumericProperty(0) moves = NumericProperty(0) current_player = StringProperty('ck') # instead of self.turn! run = NumericProperty(1) total_runs = NumericProperty() history_record = {'move': '', 'hand': '', 'up': '', 'target': ''} game_card_file = StringProperty('data/card_back_blue.jpeg') """ Default covered cards for each position on table""" TTT_COVERED_CARDS_ZONES = {"Up Position": False, "DownC Position": True, "DownN Position": True, "Target Position": False, "Color Position": False, "Number Position": False} """'linear' card layout. See 'format' parameter in config file.""" LINEAR_CARD_LAYOUT = ["DownC Position", "Color Position", "Up Position", "Target Position", "DownN Position", "Number Position"] """'circular' card layout. This is the default format. See 'format' parameter in config file.""" CIRCULAR_CARD_LAYOUT = ["Number Position", "DownN Position", "Up Position", "DownC Position", "Color Position", "Target Position"] """file names for each card. The file holds the corresponding card picture. It follows the order in HOME_CARDS_COORDINATES""" cards_files = ["h2red.jpeg", "h3red.jpeg", "h4red.jpeg", "c2black.jpeg", "c3black.jpeg", "c4black.jpeg"] """card names, they corresponds to the card agent names""" cards_names = ["2H", "3H", "4H", "2C", "3C", "4C"] """zones names here follow the order in HOME_CARDS_COORDINATES array""" cards_zones_names = ["Color Position", "Target Position", "Number Position", "DownC Position", "Up Position", "DownN Position"] """Allowed zones in which players can exchange/move cards. The 'Target' zone is subjected to extra restrictions.""" allowed_moving_zones = ["Up Position", "Target Position", "DownC Position", "DownN Position"] """map zones -> moves""" ZONES_MOVES = {"Up Position": "U", "DownC Position": "C", "DownN Position": "N", "Target Position": "T", "Pass": "******"} """map card name -> card picture file""" cards_names_files = {"2H": "h2red.jpeg", "3H": "h3red.jpeg", "4H": "h4red.jpeg", "2C": "c2black.jpeg", "3C": "c3black.jpeg", "4C": "c4black.jpeg"} """map card name -> color """ cards_colors = {"2H": "red", "3H": "red", "4H": "red", "2C": "black", "3C": "black", "4C": "black"} """map card name -> number """ cards_numbers = {"2H": 2, "3H": 3, "4H": 4, "2C": 2, "3C": 3, "4C": 4} """Map zone name -> card name. Tracks the zone in which a card is located. It is updated according to the game moves.""" zones_cards = dict() help_txt = open('data/help_eng.txt', 'r').read() hand_history = [] # history related to the current hand def __init__(self, *pars, **kpars): AnchorLayout.__init__(self, *pars, **kpars) self.cur_dir = dirname(abspath('file')) config = Configuration() config.purgelines() config.initialize() # do not use '-' or other chars in the name! self.username = '' self.turnables = config.getParam('turnable') self.cards_unturnable = bool(int(config.getParam('cards_unturnable'))) # enables the automatic player agent, playing as NK as default. self.rp = None # Rule Parser self.auto_player = bool(int(config.getParam('auto_player'))) if self.auto_player: self.rp = RuleParser() self.rp.load_rules() self.format = config.getParam('format') self.help_file = config.getParam('help_file') self.shoe_file = config.getParam('shoe_file') self.srv = config.getParam('server') self.srv_port = config.getParam('server_port') if self.format == 'circular': self.turnable = dict(zip(TTTGame.CIRCULAR_CARD_LAYOUT, [bool(int(x)) for x in self.turnables.split(',')])) else: self.turnable = dict(zip(TTTGame.LINEAR_CARD_LAYOUT, [bool(int(x)) for x in self.turnables.split(',')])) self.timer_start = int(config.getParam('timer_start')) # load default shoe_file: self.shoe_config = Configuration(config_file=self.shoe_file) self.shoe_config.purgelines() # self.turn = '' # current player turn self.current_target_card = '' # the target card stated in the shoe_file ("2H" usually) print self.shoe_config.content self.hands = [] # store all hands # file names are in the form: output-<dmY>-<HM>.txt # Here we just create a reference. The actual obj is made when the login popup is dismissed self.fout_handle = None self.fout_time_handle = None self.timeHistory = [] # list holding the strings: <move> <time>\n self.stopwatch = StopWatch() self.nk_history_q = deque([], 2) # nk history of the last 2 hands. Required for automatic agent self.ck_history_q = deque([], 2) # ck history of the last 2 hands. Required for automatic agent def update(self, dt): pass def post_init(self): self.total_runs = len(self.shoe_config.content) self.connection_popup = ConnectionPopup() self.connection_popup.open() # self.connection_popup() # self.show_login_popup() # self.generate_hand() def count_down(self, seconds): """Manage the count down clock.""" m, s = divmod(seconds, 60) h, m = divmod(m, 60) self.timer.text = "Timer: %d:%02d:%02d" % (h, m, s) if seconds > 0: Clock.schedule_once(lambda dt: self.count_down(seconds - 1), 1) else: self.quit_popup() # end game def save_local_data(self): """Save data locally about moves. The format is the following: <current hand>\n \n <moves>\n \n """ hand_s = '' for x in self.hand[:6]: # only first 6 elements hand_s = hand_s + x + ' ' hand_s = hand_s.strip() self.fout_handle.write(hand_s + '\n\n') self.fout_handle.flush() s = '' for x in self.hand_history: s = s + x s = s.strip() s = s.upper() self.fout_handle.write(s + '\n\n') self.fout_handle.flush() s = '' self.fout_time_handle.write(hand_s + '\n\n') for x in self.timeHistory: x = x.strip() x = x.upper() self.fout_time_handle.write(x + '\n') self.fout_time_handle.write('\n') self.fout_time_handle.flush() def save_data_remote(self): s = '' for x in self.hand[:6]: # only first 6 elements s = s + x + ' ' s = s.strip() s = s + '\n\n' h = '' for x in self.hand_history: h = h + x h = h.strip() h = h.upper() # print h # s = s + h + '\n' ap.App.get_running_app().log(s + h + '\n', 'MOVES') for x in self.timeHistory: x = x.strip() x = x.upper() s = s + x + '\n' ap.App.get_running_app().log(s, 'TIMES') def cover_cards(self): """ Cover all cards on the table. Cards show the back picture file. """ print "COVER CARDS" for w in self.game_table.children: print "child" if isinstance(w, CardWidget): print "covering: %s" % w.name w.to_front = False def show_cards(self, game_rules_on=False): """ Show all cards on the table. Cards show the front picture file. if 'game_rules_on', cards are turned face up only if the game rules allows. """ for w in self.game_table.children: if isinstance(w, CardWidget): if game_rules_on: if not TTTGame.TTT_COVERED_CARDS_ZONES[w.zone]: print "card in zone %s moved to front" % w.zone w.to_front = True else: print "card in zone %s moved to back" % w.zone w.to_front = False if w.zone == 'Color Position' and self.current_player == 'nk': w.to_front = False if w.zone == 'Number Position' and self.current_player == 'ck': w.to_front = False else: w.to_front = True def score(self, current_card=None, opponent_card=None, move='Pass', no_auto_player_trigger=False): """Score a valid move for the current player and updates stats and the current player turn. Moves are added to the hand's history of moves and to the short term memory history as well. Cards in NumberKeeper and ColorKeeper positions are flip according to the current player. If the auto player is enabled, its behavior is triggered with a delay. If no_auto_player_trigger then the auto player is not triggered. """ self.moves += 1 self.total_moves += 1 move_time = self.stopwatch.split() move_str = '' h_record = dict(TTTGame.history_record) # make a new one # Search who is in the target and up positions for w in self.game_table.children: if isinstance(w, CardWidget): if w.zone == 'Target Position': cur_target = w.name # print "cur target: ",cur_target elif w.zone == 'Up Position': cur_up = w.name # print "cur up: ",cur_up elif w.zone == 'Color Position' and self.current_player == 'ck': cur_hand = w.name # print "cur hand: ",cur_hand elif w.zone == 'Number Position' and self.current_player == 'nk': cur_hand = w.name # print "cur hand",cur_hand elif w.zone == 'DownC Position': cur_c = w.name # print "cur c ", cur_c elif w.zone == 'DownN Position': cur_n = w.name # print "cur n: ",cur_n else: print "Unmatched card zone string: ", w.zone if current_card == None and opponent_card == None: move_str = TTTGame.ZONES_MOVES[move] + ' ' + self.stopwatch.to_string(move_time) TTTGame.hand_history.append(TTTGame.ZONES_MOVES[move]) # No move, the same as before: h_record['move'] = TTTGame.ZONES_MOVES[move] h_record['up'] = cur_up h_record['target'] = cur_target h_record['hand'] = cur_hand else: move_str = TTTGame.ZONES_MOVES[current_card.zone] + ' ' + self.stopwatch.to_string(move_time) TTTGame.hand_history.append(TTTGame.ZONES_MOVES[current_card.zone]) h_record['move'] = TTTGame.ZONES_MOVES[current_card.zone] # when the move is 't' or 'u', simply swap the card name in hand # position (relative to the color or number keeper) with the respective position (target or up). if h_record['move'] == 'T': h_record['up'] = cur_up h_record['target'] = cur_hand h_record['hand'] = cur_target elif h_record['move'] == 'U': h_record['up'] = cur_hand h_record['target'] = cur_target h_record['hand'] = cur_up print h_record elif h_record['move'] == 'N': h_record['up'] = cur_up h_record['target'] = cur_target h_record['hand'] = cur_n elif h_record['move'] == 'C': h_record['up'] = cur_up h_record['target'] = cur_target h_record['hand'] = cur_c else: print "Unmatched move when extracting history situation." self.timeHistory.append(move_str) # swap players for the next round: if self.current_player == 'ck': self.ck_history_q.appendleft(h_record) self.current_player = 'nk' # when auto player enabled trigger its behavior in [1:3] seconds delay if self.auto_player and not no_auto_player_trigger: Clock.schedule_once(lambda dt: self.auto_player_behavior(), random.randint(1, 3)) else: self.nk_history_q.appendleft(h_record) self.current_player = 'ck' self.adjust_keeper_cards() # flip the opponent card self.adjust_cards_border() # highlight the player card def adjust_cards_border(self): """ Highlight the player card's. """ # resets for sure: not nice programming! :-( for w in [x for x in self.game_table.children if isinstance(x, CardWidget)]: w.show_border(off=True) # resets for w in [x for x in self.game_table.children if isinstance(x, CardWidget)]: if w.zone == 'Color Position' and self.current_player == 'ck': w.show_border() elif w.zone == 'Number Position' and self.current_player == 'nk': w.show_border() else: # pass w.show_border(off=True) def adjust_keeper_cards(self): """Flip a keeper card according to the current player. """ for w in [x for x in self.game_table.children if isinstance(x, CardWidget) and (x.zone == 'Color Position' or x.zone == 'Number Position')]: if w.zone == 'Color Position': if self.current_player == 'nk': w.to_front = False else: w.to_front = True if w.zone == 'Number Position': if self.current_player == 'ck': w.to_front = False else: w.to_front = True def relocate(self, card, other_card): """Set the new zone for the current and companion card and set up the animations for both cards. """ card_temp_x = card.old_x card_temp_y = card.old_y other_card_temp_x = other_card.x other_card_temp_y = other_card.y # print "selfzone: ", self.zone temp_zone = card.zone # print "tempzone: ", temp_zone # print "w zone: ", other_card.zone anim1 = Animation(pos=(other_card_temp_x, other_card_temp_y), t='in_quad', d=0.5) anim2 = Animation(pos=(card_temp_x, card_temp_y), t='in_quad', d=0.5) anim1.start(card) anim2.start(other_card) card.zone = other_card.zone other_card.zone = temp_zone def is_goal(self, card, other_card): """ Check whether the move accomplish the hand goal or not""" if other_card.zone == 'Target Position': if card.zone == 'Color Position' and card.color == other_card.color: print "card zone: %s , color: %s, other color: %s" % (card.zone, card.color, other_card.color) return True if card.zone == 'Number Position' and card.number == other_card.number: print "card zone: %s , color: %s, other color: %s" % (card.zone, card.number, other_card.number) return True return False def check_cards_collision(self, touch, card, other_card): """Check if the move is correct and if the hand is complete. :param touch: a touch object :param card: moved card :param other_card: card over which has been moved the other :return: wether or not the other_card must be relocated """ relocated = False if other_card.collide_point(touch.x, touch.y) and other_card.zone in TTTGame.allowed_moving_zones: # collision! if other_card.zone == 'Target Position': if self.is_goal(card, other_card): # check if its is the current target print "NAME: %s - target: %s" % (card.name, self.current_target_card) if card.name == self.current_target_card: # ended current run! self.run += 1 self.console.text += 'Hand successful!\n' Clock.schedule_once(lambda dt: self.generate_hand()) self.relocate(card, other_card) relocated = True self.score(card, other_card, no_auto_player_trigger=True) else: self.relocate(card, other_card) relocated = True self.score(card, other_card) else: self.relocate(card, other_card) relocated = True self.score(card, other_card) return relocated def generate_hand(self): """Generate a new handle from the description found in the shoe file. the hand is injected in the hand property. Before generating the handle: - manage the count-down clock. - save local and remote data - generate the popup to signal the new hand to the user """ # self.cover_cards() run_index = self.run - 1 self.total_runs = len(self.shoe_config.content) if not self.stopwatch.running: self.stopwatch.start() if run_index < len(self.shoe_config.content): # hand available if run_index >= 1: if self.stopwatch.running: self.stopwatch.stop() self.save_local_data() self.save_data_remote() self.stopwatch.reset() self.stopwatch.start() self.hand = self.shoe_config.content[run_index].split() cards = [x for x in self.game_table.children if isinstance(x, CardWidget)] # self.console.text += 'New hand: ' + str(self.hand[:6]) + '\n\n' print "New hand: ", self.hand nk = dn = up = dc = ck = t = gc = cur_player = '' if len(self.hand) != 8: print "Invalid line in shoe file '%s' : %s" % (self.shoe_file, self.hand) elif self.format == 'circular': nk, dn, up, dc, ck, t, gc, cur_player = self.hand else: # linear layout dn, ck, up, t, dn, t, gc, cur_player = self.hand # self.turn = cur_player self.current_player = cur_player self.hand_history[:] = [] # resets the history for the next hand self.timeHistory[:] = [] # resets the time history of each move for c in cards: if c.zone == 'Color Position': c.front_pic_file = 'data/' + TTTGame.cards_names_files[ck] c.color = TTTGame.cards_colors[ck] c.name = ck c.number = TTTGame.cards_numbers[ck] elif c.zone == 'Target Position': c.front_pic_file = 'data/' + TTTGame.cards_names_files[t] c.color = TTTGame.cards_colors[t] c.name = t c.number = TTTGame.cards_numbers[t] c.to_front = True elif c.zone == 'Number Position': c.front_pic_file = 'data/' + TTTGame.cards_names_files[nk] c.color = TTTGame.cards_colors[nk] c.name = nk c.number = TTTGame.cards_numbers[nk] elif c.zone == 'DownC Position': c.front_pic_file = 'data/' + TTTGame.cards_names_files[dc] c.color = TTTGame.cards_colors[dc] c.name = dc c.number = TTTGame.cards_numbers[dc] elif c.zone == 'Up Position': c.front_pic_file = 'data/' + TTTGame.cards_names_files[up] c.color = TTTGame.cards_colors[up] c.name = up c.number = TTTGame.cards_numbers[up] c.to_front = True elif c.zone == 'DownN Position': c.front_pic_file = 'data/' + TTTGame.cards_names_files[dn] c.color = TTTGame.cards_colors[dn] c.name = dn c.number = TTTGame.cards_numbers[dn] else: print "Warning: unknown position '%s'!" % c.zone # check if the card has changed before the popup if run_index >= 1: callback = None if self.auto_player and self.current_player == 'nk': callback = self.intra_hand_popup_dismiss if self.current_target_card != self.hand[6]: self.intra_hand_popup(goal_changed=True, dismiss=callback) else: self.intra_hand_popup(dismiss=callback) self.game_card_file = 'data/' + TTTGame.cards_names_files[gc] self.current_target_card = gc self.show_cards(game_rules_on=True) self.adjust_cards_border() self.moves = 0 # reset current hand moves else: # run out of runs! self.save_local_data() self.save_data_remote() self.quit_popup() def auto_player_behavior(self): current_table = dict(TTTGame.history_record.items()) for w in self.game_table.children: if isinstance(w, CardWidget): if w.zone == 'Target Position': current_table['target'] = w.name elif w.zone == 'Up Position': current_table['up'] = w.name elif w.zone == 'Color Position' and self.current_player == 'ck': current_table['hand'] = w.name elif w.zone == 'Number Position' and self.current_player == 'nk': current_table['hand'] = w.name else: print "Unmatched card zone string: ", w.zone #======================================================================= # print "current table: ", current_table # print "ck history: ", self.ck_history_q # print "nk history: ", self.nk_history_q #======================================================================= rule = self.rp.match(current_table['hand'], current_table['up'], current_table['target'], self.ck_history_q, self.nk_history_q, self.history_record) self.console.text += "rule selected: %s\n" % rule[0] self.apply_move(rule[0], current_table['hand']) def apply_move(self, move, hand): """Used by the auto player: actually performs the move dictated by the rules. """ if move == 'p': self.score(None, None) else: card = None to_card = None for w in self.game_table.children: if isinstance(w, CardWidget): if w.name == hand: card = w if (w.zone != 'Color Position' and w.zone != 'Number Position') \ and TTTGame.ZONES_MOVES[w.zone] == move: to_card = w if to_card.zone == 'Target Position': if self.is_goal(card, to_card): if card.name == self.current_target_card: self.run += 1 self.console.text += 'Hand successful!\n' Clock.schedule_once(lambda dt: self.generate_hand()) card.select() self.relocate(card, to_card) card.selected = None self.score(card, to_card) def show_login_popup(self): l_popup = LoginPopup() l_popup.open() def intra_hand_popup(self, goal_changed=False, dismiss=None): """Generate a popup to emphasize the start of a new hand. By clicking over the button, it disappears. When """ popup = Popup(title='Next Hand Window', size_hint=(0.3, 0.3)) if dismiss == None: dismiss = popup.dismiss l = BoxLayout(orientation='vertical') if goal_changed: l.add_widget(Label(text='Your Goal card has changed', font_size='30sp')) l.add_widget(Button(text='Next hand', font_size='40sp', on_press=dismiss)) popup.add_widget(l) popup.open() def intra_hand_popup_dismiss(self, widget): Clock.schedule_once(lambda dt: self.auto_player_behavior(), random.randint(1, 3)) widget.parent.parent.parent.parent.dismiss() def quit_popup(self): popup = Popup(title='Score Summary Window', size_hint=(0.7, 0.7)) l = BoxLayout(orientation='vertical') l.add_widget(Label(text='Game Over', font_size='40sp')) l.add_widget(Label(text='Total moves played: %d' % self.total_moves, font_size='25sp')) popup.add_widget(l) popup.bind(on_dismiss=self.summary_popup_dismiss) popup.open() def summary_popup_dismiss(self, *pars): sys.exit() def help_on_press_callback(self): """Callback for 'help' button. """ popup = Popup(title='Help Window', content=TextInput(text=unicode(TTTGame.help_txt, errors='replace'), readonly=True, auto_indent=True, cursor=(1, 1)), size_hint=(None, None), size=(400, 400)) popup.open() def pass_on_press_callback(self): """Callback for 'pass' button. """ self.score(None, None) def connect_on_press_callback(self): """Tries to connect to the server using first the network discovery service. If it fails, try the address in config.txt. If it fails again: no connection. The exit status is displayed on the right console widget. """ s = ap.App.get_running_app().mp.servers if s != None and len(s) >= 1: print "server found: %s" % s[0] self.srv = s[0] # ap.App.get_running_app().connect_to_server(self.srv, 8080) # else: ap.App.get_running_app().connect_to_server(self.srv, 8080)