diff options
author | Walter Bender <walter.bender@gmail.com> | 2011-03-20 17:16:01 (GMT) |
---|---|---|
committer | Walter Bender <walter.bender@gmail.com> | 2011-03-20 17:16:01 (GMT) |
commit | f0ab2c46c0fe3f26ce10d7a07e59fba2b612d166 (patch) | |
tree | adbb38ec49ac0a5b155e4cdf336f98bdb909458b | |
parent | f84386784d4f447fb20b00bceb5f2f731e45c65b (diff) |
first rough pass at collaboration
-rw-r--r-- | PathsActivity.py | 152 | ||||
-rw-r--r-- | card.py | 9 | ||||
-rw-r--r-- | deck.py | 5 | ||||
-rw-r--r-- | game.py | 166 | ||||
-rw-r--r-- | grid.py | 17 | ||||
-rw-r--r-- | hand.py | 31 | ||||
-rw-r--r-- | utils.py | 15 |
7 files changed, 296 insertions, 99 deletions
diff --git a/PathsActivity.py b/PathsActivity.py index 68f8e1e..50c9377 100644 --- a/PathsActivity.py +++ b/PathsActivity.py @@ -45,7 +45,7 @@ import os.path from game import Game, CARDS from hand import Hand -from utils import data_from_string, data_to_string +from utils import json_load, json_dump ROW = 8 COL = 8 @@ -109,6 +109,7 @@ class PathsActivity(activity.Activity): else: self.colors = ['#A0FFA0', '#FF8080'] self._setup_toolbars(_have_toolbox) + self._setup_dispatch_table() # Create a canvas canvas = gtk.DrawingArea() @@ -119,18 +120,28 @@ class PathsActivity(activity.Activity): self.show_all() self._game = Game(canvas, parent=self, colors=self.colors) + self._setup_presence_service() # Restore game state from Journal or start new game if 'deck0' in self.metadata: + print 'restoring' self._restore() - else: + elif not hasattr(self, 'initiating'): + print 'no initiating' + self._game.new_game() + elif not self.initiating: + print 'I am not initiating' self._game.new_game() + elif len(self._game.buddies) == 1: + print 'no buddies' + self._game.new_game() + else: + print 'I am confused' def _setup_toolbars(self, have_toolbox): """ Setup the toolbars.. """ - # no sharing - self.max_participants = 1 + self.max_participants = 4 if have_toolbox: toolbox = ToolbarBox() @@ -155,12 +166,6 @@ class PathsActivity(activity.Activity): toolbox.set_current_toolbar(1) toolbar = games_toolbar - # no sharing - if hasattr(toolbox, 'share'): - toolbox.share.hide() - elif hasattr(toolbox, 'props'): - toolbox.props.visible = False - self._new_game_button = _button_factory('new-game', _('Start a new game.'), self._new_game_cb, toolbar) @@ -169,6 +174,10 @@ class PathsActivity(activity.Activity): _('Play with the computer.'), self._robot_cb, toolbar) + self.dialog_button = _button_factory('dialog-ok', + _('Turn complete'), + self._dialog_cb, toolbar) + self.status = _label_factory(_('It is your turn.'), toolbar) if _have_toolbox: @@ -185,14 +194,11 @@ class PathsActivity(activity.Activity): def _robot_cb(self, button=None): ''' Play with the computer (or not). ''' - print 'playing with robot:', self._game.playing_with_robot if not self._game.playing_with_robot: self.set_robot_status(True, 'robot-on') - print 'starting new game with robot' self._game.new_game() else: self.set_robot_status(False, 'robot-off') - print 'starting new game without robot' self._game.new_game() def set_robot_status(self, status, icon): @@ -200,6 +206,13 @@ class PathsActivity(activity.Activity): self._game.playing_with_robot = status self.robot_button.set_icon(icon) + def _dialog_cb(self, button=None): + ''' Send end of turn ''' + if self._game.placed_a_tile: + print 'finished a turn' + else: + print 'need to place a piece' + def write_file(self, file_path): """ Write the grid status to the Journal """ if not hasattr(self, '_game'): @@ -251,7 +264,7 @@ class PathsActivity(activity.Activity): owner = self.pservice.get_owner() self.owner = owner - self._game.buddies.append(self.owner) + self._game.buddies.append(self.nick) self._share = "" self.connect('shared', self._shared_cb) self.connect('joined', self._joined_cb) @@ -259,13 +272,13 @@ class PathsActivity(activity.Activity): def _shared_cb(self, activity): """ Either set up initial share...""" if self._shared_activity is None: - _logger.error("Failed to share or join activity ... \ + print("Error: Failed to share or join activity ... \ _shared_activity is null in _shared_cb()") return self.initiating = True self.waiting_for_hand = False - _logger.debug('I am sharing...') + print('I am sharing...') self.conn = self._shared_activity.telepathy_conn self.tubes_chan = self._shared_activity.telepathy_tubes_chan @@ -274,19 +287,19 @@ class PathsActivity(activity.Activity): self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal\ ('NewTube', self._new_tube_cb) - _logger.debug('This is my activity: making a tube...') + print('This is my activity: making a tube...') id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube( SERVICE, {}) def _joined_cb(self, activity): """ ...or join an exisiting share. """ if self._shared_activity is None: - _logger.error("Failed to share or join activity ... \ + print("Error: Failed to share or join activity ... \ _shared_activity is null in _shared_cb()") return self.initiating = False - _logger.debug('I joined a shared activity.') + print('I joined a shared activity.') self.conn = self._shared_activity.telepathy_conn self.tubes_chan = self._shared_activity.telepathy_tubes_chan @@ -295,11 +308,12 @@ class PathsActivity(activity.Activity): self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(\ 'NewTube', self._new_tube_cb) - _logger.debug('I am joining an activity: waiting for a tube...') + print('I am joining an activity: waiting for a tube...') self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes( reply_handler=self._list_tubes_reply_cb, error_handler=self._list_tubes_error_cb) + # DO TO: DISABLE NEW GAME BUTTON self.waiting_for_hand = True def _list_tubes_reply_cb(self, tubes): @@ -309,13 +323,12 @@ class PathsActivity(activity.Activity): def _list_tubes_error_cb(self, e): """ Log errors. """ - _logger.error('ListTubes() failed: %s', e) + print('Error: ListTubes() failed: %s', e) def _new_tube_cb(self, id, initiator, type, service, params, state): """ Create a new tube. """ - _logger.debug('New tube: ID=%d initator=%d type=%d service=%s ' - 'params=%r state=%d', id, initiator, type, service, - params, state) + print('New tube: ID=%d initator=%d type=%d service=%s params=%r \ +state=%d' % (id, initiator, type, service, params, state)) if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE): if state == telepathy.TUBE_STATE_LOCAL_PENDING: @@ -331,38 +344,108 @@ class PathsActivity(activity.Activity): # Let the sharer know joiner is waiting for a hand. if self.waiting_for_hand: - self._send_event('j|[%s]' % (data_to_string(self.nick))) + print 'send event joining (%s)' % (self.nick) + self.send_event('j|%s' % (self.nick)) def _setup_dispatch_table(self): self._processing_methods = { - 'j': self._new_joiner, - 'b': self._buddy_list + 'n': [self._new_game, 'new game'], + 'j': [self._new_joiner, 'new joiner'], + 'b': [self._buddy_list, 'buddy list'], + 'd': [self._sending_deck, 'sending deck'], + 'h': [self._sending_hand, 'sending hand'], + 'p': [self._play_a_piece, 'play a piece'], + 't': [self._take_a_turn, 'take a turn'] } def event_received_cb(self, event_message): if len(event_message) == 0: return try: + print 'event received', event_message command, payload = event_message.split('|', 2) - self._processing_methods[command](payload) except ValueError: - _logger.debug('Could not split event message %s' % (event_message)) + print('Could not split event message %s' % (event_message)) + return + print 'calling event', self._processing_methods[command][1] + self._processing_methods[command][0](payload) def _new_joiner(self, payload): - nick = data_from_string(payload) - _logger.debug("%s has joined" % (nick)) + print payload + nick = payload + print("%s has joined" % (nick)) + self.status.set_label(nick + ' ' + _('has joined.')) if not nick in self._game.buddies: self._game.buddies.append(nick) if self.initiating: - self._send_event('b|%s' % (data_to_string(self._game.buddies))) + payload = json_dump(self._game.buddies) + print payload + self.send_event('b|%s' % (payload)) def _buddy_list(self, payload): - buddies = data_from_string(payload) + buddies = json_load(payload) for nick in buddies: if not nick in self._game.buddies: self._game.buddies.append(nick) + print 'appending %s to buddy list' % (nick) + + def _new_game(self, payload): + if not self.initiating: + print 'starting new game' + self._game.new_game() + + def _sending_deck(self, payload): + print 'got a deck' + self._game.deck.restore(payload) + print 'reseting cards in new deck' + for card in self._game.deck.cards: + card.reset() + card.hide() + + def _sending_hand(self, payload): + hand = json_load(payload) + nick = hand[0] + if nick == self.nick: + print 'I got a hand' + self._game.hands[self._game.buddies.index(nick)].restore( + payload, self._game.deck, buddy=True) + else: + print 'I saw a hand for %s' % (nick) + + def _play_a_piece(self, payload): + # TO DO: something with buttons and label + tile_number, orientation, grid_position = json_load(payload) + print 'moving tile', tile_number, orientation, grid_position + for i in range(ROW * COL): # find the tile with this number + if self._game.deck.cards[i].number == tile_number: + tile_to_move = i + break + self._game.grid.add_card_to_grid(tile_to_move, orientation, + grid_position, self._game.deck) + self._game.show_connected_tiles() + if self.initiating: + self._game.whos_turn += 1 + if self._game.whos_turn == len(self._game.buddies): + self._game.whos_turn = 0 + print "it is %s's turn" % (self._game.buddies[self._game.whos_turn]) + print 'sending turn event' + self.send_event('t|%s' % (self._game.buddies[self._game.whos_turn])) + self._take_a_turn(self._game.buddies[self._game.whos_turn]) + + def _take_a_turn(self, payload): + # TO DO: something with buttons and label + print 'take a turn event' + nick = payload + print "It's %s's turn." % (nick) + self.status.set_label(nick + ': ' + _('take a turn.')) + if nick == self.nick: + self.dialog_button.set_icon('dialog-ok') + self._game.its_my_turn() + else: + self.dialog_button.set_icon('dialog-cancel') + self._game.its_their_turn(nick) - def _send_event(self, entry): + def send_event(self, entry): """ Send event through the tube. """ if hasattr(self, 'chattube') and self.chattube is not None: self.chattube.SendText(entry) @@ -390,3 +473,4 @@ class ChatTube(ExportedGObject): @signal(dbus_interface=IFACE, signature='s') def SendText(self, text): self.stack = text + print text @@ -17,6 +17,7 @@ N = 0 E = N + 1 S = E + 1 W = S + 1 +HIDE = 0 CARDS = 3 @@ -32,6 +33,9 @@ class Card: self.orientation = 0 self.type = card_type self.number = number + self.spr.set_label_color('#FF0000') + self.spr.set_label_attributes(32) + self.spr.set_label(str(number)) def set_paths(self, paths): for c in paths: @@ -41,8 +45,11 @@ class Card: return self.paths def reset(self): + self.spr.set_layer(HIDE) self.shape = None self.spr.set_shape(self.highlight[0]) + while self.orientation != 0: + self.rotate_clockwise() def set_shape(self, path): if self.shape is None: @@ -71,7 +78,7 @@ class Card: self.spr.draw() def hide_card(self): - self.spr.hide() + self.spr.move((-self.spr.images[0].get_width(),0)) # @@ -83,6 +83,7 @@ class Deck: [color, color])], number=i)) self.cards[-1].set_paths([[1, 1, 0, 0], [0, 0, 0, 1]]) i += 1 + # Remember the current position in the deck. self.index = 0 @@ -93,9 +94,9 @@ class Deck: def shuffle(self): ''' Shuffle the deck (Knuth algorithm). ''' decksize = self.count() - # Hide all the cards. + # Hide all the cards and make sure they are back to orientation 0 for c in self.cards: - c.spr.set_layer(HIDE) + c.reset() # Randomize the card order. for n in range(decksize): i = randrange(decksize - n) @@ -26,6 +26,7 @@ from grid import Grid from hand import Hand from deck import Deck from card import error_card, highlight_cards +from utils import json_dump from sprites import Sprites ROW = 8 @@ -45,6 +46,7 @@ OVERLAY = 4 MY_HAND = 0 ROBOT_HAND = 1 + class Game(): def __init__(self, canvas, parent=None, colors=['#A0FFA0', '#FF8080']): @@ -97,66 +99,160 @@ class Game(): self.press = None self.release = None self.last_spr_moved = None - self.playing_with_robot = False self.placed_a_tile = False - + self.last_tile_played = None + self.last_tile_orientation = 0 + self.last_grid_played = None self.buddies = [] + self.my_hand = MY_HAND + self.whos_turn = 0 + self.waiting_for_my_turn = False def new_game(self, saved_state=None, deck_index=0): ''' Start a new game. ''' + print 'starting new game' + # If there is already a deck, hide it. if hasattr(self, 'deck'): self.deck.hide() + print 'hiding the deck' + # Shuffle the deck and deal a hand of tiles. self.grid.clear() self.deck.clear() self.show_connected_tiles() - self.deck.shuffle() for hand in self.hands: hand.clear() - self.hands[MY_HAND].deal(self.deck) - if self.playing_with_robot: - if len(self.hands) < ROBOT_HAND + 1: - self.hands.append(Hand(self.card_width, self.card_height, - remote=True)) - self.hands[ROBOT_HAND].deal(self.deck) + + print 'everything is clear' + + # If we are not sharing or we are the sharer... + if not self._we_are_sharing() or self.activity.initiating: + if not self._we_are_sharing(): + print 'We are not sharing.' + if not self.activity.initiating: + print 'I am initiating.' + # Let joiners know we are starting a new game... + if self._we_are_sharing(): + print 'sending a new_game event' + self.activity.send_event('n| ') + self.deck.shuffle() + # ...and share the deck. + if self._we_are_sharing(): + print 'sending a new deck event' + self.activity.send_event('d|%s' % (self.deck.serialize())) + + # Deal a hand to yourself... + print 'dealing myself a hand' + self.hands[self.my_hand].deal(self.deck) + print 'my hand', self.hands[self.my_hand] + + # ...deal a hand to the robot + if self.playing_with_robot: + print 'dealing robot a hand' + if len(self.hands) < ROBOT_HAND + 1: + self.hands.append(Hand(self.card_width, self.card_height, + remote=True)) + self.hands[ROBOT_HAND].deal(self.deck) + # ...or deal hands to the joiners. + elif len(self.buddies) > 1 and self.activity.initiating: + for i, buddy in enumerate(self.buddies): + if buddy != self.activity.nick: + self.hands.append(Hand( + self.card_width, self.card_height, remote=True)) + self.hands[i].deal(self.deck) + print 'dealing %s a hand' % (buddy) + self.activity.send_event('h|%s' % \ + (self.hands[i].serialize(buddy=buddy))) + # Or wait for a hand. + else: + self.my_hand = self.buddies.index(self.activity.nick) + print 'Waiting for hand from the sharer.' + self.press = None self.release = None self.placed_a_tile = None self.last_spr_moved = None + self.last_tile_played = None + self.last_tile_orientation = 0 + self.last_grid_played = None self._hide_highlight() self._hide_errormsgs() + self.whos_turn = 0 + self.its_my_turn() + self.waiting_for_my_turn = False + + def _we_are_sharing(self): + if len(self.buddies) > 1: + return True + + def its_my_turn(self): + self.waiting_for_my_turn = False + if self.hands[self.my_hand].cards_in_hand() == 0: + self._redeal() + if self.sugar: + self.activity.status.set_label(_('It is my turn.')) + self.placed_a_tile = False + + def _redeal(self): + print 'redeal' + if not self._we_are_sharing(): + for hand in self.hands: + hand.deal(self.deck) + elif self.activity.initiating: + for i, buddy in enumerate(self.buddies): + print 'dealing %s a hand' % (buddy) + self.hands[i].deal(self.deck) + if buddy != self.activity.nick: + self.activity.send_event('h|%s' % \ + (self.hands[i].serialize(buddy=buddy))) + + def took_my_turn(self): + self.waiting_for_my_turn = True + if self.sugar: + self.activity.status.set_label(_('I took my turn.')) + if self._we_are_sharing(): + self.activity.send_event('p|%s' % \ + (json_dump([self.last_tile_played, + self.last_tile_orientation, + self.last_grid_played]))) + + def its_their_turn(self, nick): + if self.sugar: + self.activity.status.set_label(_('Waiting for ') + nick) def _button_press_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) + + if self.waiting_for_my_turn: + print "waiting for my turn" + return + self.start_drag = [x, y] spr = self.sprites.find_sprite((x, y)) self.press = None self.release = None - # Ignore clicks on background. + # Ignore clicks on background except to indicate you took your turn if spr is None or \ spr in self.grid.blanks or \ spr == self.deck.board: if self.placed_a_tile and spr is None: + self.took_my_turn() if self.playing_with_robot: + self.its_their_turn(_('robot')) self._robot_play() self.show_connected_tiles() - if self.hands[MY_HAND].cards_in_hand() == 0: - for hand in self.hands: - hand.deal(self.deck) - if self.playing_with_robot and self.sugar: - self.activity.status.set_label(_('It is your turn.')) - self.placed_a_tile = False + self.its_my_turn() return True # Are we clicking on a tile in the hand? - if self.hands[MY_HAND].spr_to_hand(spr) is not None and \ + if self.hands[self.my_hand].spr_to_hand(spr) is not None and \ not self.there_are_errors: self.last_spr_moved = spr if self.sugar: @@ -164,10 +260,7 @@ class Game(): if self.placed_a_tile: if self.playing_with_robot: self._robot_play() - if self.hands[MY_HAND].cards_in_hand() == 0: - for hand in self.hands: - hand.deal(self.deck) - self.placed_a_tile = False + self.its_my_turn() else: clicked_in_hand = False @@ -181,6 +274,10 @@ class Game(): def _button_release_cb(self, win, event): win.grab_focus() + if self.waiting_for_my_turn: + print "waiting for my turn" + return + if self.press is None: return @@ -188,17 +285,17 @@ class Game(): spr = self.sprites.find_sprite((x, y)) if spr is None: # Returning tile to hand - i = self.hands[MY_HAND].find_empty_slot() + i = self.hands[self.my_hand].find_empty_slot() if i is not None: card = self.deck.spr_to_card(self.press) - card.spr.move(self.hands[MY_HAND].hand_to_xy(i)) - if self.hands[MY_HAND].spr_to_hand(self.press) is not None: - self.hands[MY_HAND].hand[ - self.hands[MY_HAND].spr_to_hand(self.press)] = None + card.spr.move(self.hands[self.my_hand].hand_to_xy(i)) + if self.hands[self.my_hand].spr_to_hand(self.press) is not None: + self.hands[self.my_hand].hand[ + self.hands[self.my_hand].spr_to_hand(self.press)] = None elif self.grid.spr_to_grid(self.press) is not None: self.grid.grid[self.grid.spr_to_grid(self.press)] = None - self.hands[MY_HAND].hand[i] = card + self.hands[self.my_hand].hand[i] = card if spr == self.last_spr_moved: self.last_spr_moved = None self._hide_highlight() @@ -213,6 +310,8 @@ class Game(): if self.press == self.release: card = self.deck.spr_to_card(spr) card.rotate_clockwise() + print 'new orientation', card.orientation + self.last_tile_orientation = card.orientation if self.last_spr_moved != card.spr: self.last_spr_moved = card.spr self._show_highlight() @@ -226,10 +325,13 @@ class Game(): self.grid.grid[self.grid.xy_to_grid(x, y)] = card self.placed_a_tile = True + self.last_tile_played = card.number + print 'orientation', card.orientation + self.last_grid_played = self.grid.xy_to_grid(x, y) - i = self.hands[MY_HAND].spr_to_hand(self.press) + i = self.hands[self.my_hand].spr_to_hand(self.press) if i is not None: - self.hands[MY_HAND].hand[i] = None + self.hands[self.my_hand].hand[i] = None if self.last_spr_moved != card.spr: self.last_spr_moved = card.spr @@ -241,9 +343,9 @@ class Game(): self.release = None self.show_connected_tiles() - if self.hands[MY_HAND].cards_in_hand() == 0 and \ - not self.playing_with_robot: - self.hands[MY_HAND].deal(self.deck) + if self.hands[self.my_hand].cards_in_hand() == 0 and \ + not self.playing_with_robot and not self._we_are_sharing(): + self.hands[self.my_hand].deal(self.deck) return True def _game_over(self, msg=_('Game over')): @@ -74,16 +74,19 @@ class Grid: else: for k in range(ROW * COL): if deck.cards[k].number == grid[i][0]: - self.grid[i] = deck.cards[k] - self.grid[i].spr.move(self.grid_to_xy(i)) - self.grid[i].spr.set_layer(CARDS) - o = grid[i][1] - while o > 0: - self.grid[i].rotate_clockwise() - o -= 90 + self.add_card_to_grid(k, grid[i][1], i, deck) break self.show() + def add_card_to_grid(self, card_number, orientation, grid_number, deck): + ''' Add cards[card_number] to grid[grid_number] at orientation ''' + self.grid[grid_number] = deck.cards[card_number] + self.grid[grid_number].spr.move(self.grid_to_xy(grid_number)) + self.grid[grid_number].spr.set_layer(CARDS) + while orientation > 0: + self.grid[grid_number].rotate_clockwise() + orientation -= 90 + def place_a_card(self, c, x, y): ''' Place a card at position x,y and display it. ''' if c is not None: @@ -38,7 +38,6 @@ class Hand: self.left = int(card_width / 2) self.top = 0 self.yinc = int(card_height) - print 'hand: left = %d, top = %d' % (self.left, self.top) def clear(self): for i in range(COL): @@ -48,8 +47,12 @@ class Hand: ''' Deal an initial set of cards to the hand ''' for i in range(COL): self.hand[i] = deck.deal_next_card() - self.hand[i].spr.move(self.hand_to_xy(i)) - self.hand[i].spr.set_layer(CARDS) + if self.hand[i] is not None: + self.hand[i].spr.move(self.hand_to_xy(i)) + self.hand[i].spr.set_layer(CARDS) + else: + print 'No more cards in the deck.' + return def find_empty_slot(self): ''' Is there an empty slot in the hand? ''' @@ -62,9 +65,12 @@ class Hand: ''' How many cards are in the hand? ''' return COL - self.hand.count(None) - def serialize(self): + def serialize(self, buddy=None): ''' Serialize the hand for passing to share and saving ''' - hand = [] + if buddy == None: + hand = [] + else: + hand = [buddy] for i in range( COL): if self.hand[i] is not None: hand.append(self.hand[i].number) @@ -72,18 +78,23 @@ class Hand: hand.append(None) return json_dump(hand) - def restore(self, hand_as_text, deck): + def restore(self, hand_as_text, deck, buddy=False): ''' Restore cards to hand upon resume or share. ''' hand = json_load(hand_as_text) - for i in range(COL): + if buddy: + offset = 1 # skip the buddy + else: + offset = 0 + for card in range(COL): + i = card + offset if hand[i] is None: self.hand[i] = None else: for k in range(ROW * COL): if deck.cards[k].number == hand[i]: - self.hand[i] = deck.cards[k] - self.hand[i].spr.move(self.hand_to_xy(i)) - self.hand[i].spr.set_layer(CARDS) + self.hand[card] = deck.cards[k] + self.hand[card].spr.move(self.hand_to_xy(card)) + self.hand[card].spr.set_layer(CARDS) break def xy_to_hand(self, x, y): @@ -32,10 +32,7 @@ def json_load(text): listdata = json.read(text) else: # strip out leading and trailing whitespace, nulls, and newlines - clean_text = text.lstrip() - clean_text = clean_text.replace('\12', '') - clean_text = clean_text.replace('\00', '') - io = StringIO(clean_text.rstrip()) + io = StringIO(text) try: listdata = jload(io) except ValueError: @@ -45,6 +42,7 @@ def json_load(text): listdata[i] = int(value) return listdata + def json_dump(data): """ Save data using available JSON tools. """ if OLD_SUGAR_SYSTEM is True: @@ -53,12 +51,3 @@ def json_dump(data): _io = StringIO() jdump(data, _io) return _io.getvalue() - -def data_from_string(text): - """ JSON load data from a string. """ - return json_load(text.replace(']],\n', ']], ')) - -def data_to_string(data): - """ JSON dump a string. """ - return json_dump(data).replace(']], ', ']],\n') - |