oTree Forum >

Retrieving game history Live Pages

#1 by Gabriela

Hello, 
I'm working on modifying Mini Twitter. I want to run it for several rounds, and in rounds > 1, I want the game to start from wherever it stopped in the last round. I have tried saving key information (lists) as participant fields and calling them to initialised the game, which seems to work with the follow option of the game; however, when I want to unfollow a participant, it unfollows every other participant except the one I want to unfollow. It seems that when I want to unfollow someone, the lists are not being updated properly. 

The documentation suggest to retrieving the history of the game from a database; however, I'm having trouble visualising how this looks for this case. Is there any similar code that I could study and adapt? 

Thanks for your help. I'm attaching my _init_.py code below. 

Gabriela 

### CODE #####

from otree.api import *


doc = """
Mini-Twitter
"""


class C(BaseConstants):
    NAME_IN_URL = 'twitter'
    PLAYERS_PER_GROUP = None
    NUM_ROUNDS = 4


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


def get_follower_ids(player: Player, including_myself):
    ids = [sub.follower_id_in_group for sub in Subscription.filter(leader=player)]
    if including_myself:
        ids.append(player.id_in_group)
    return ids


class Subscription(ExtraModel):
    # we store the player objects and id_in_group redundantly,
    # for convenience and performance

    leader = models.Link(Player)
    leader_id_in_group = models.IntegerField()

    follower = models.Link(Player)
    follower_id_in_group = models.IntegerField()


class Message(ExtraModel):
    player = models.Link(Player)
    player_id_in_group = models.IntegerField()
    group = models.Link(Group)
    text = models.LongStringField()


def to_dict(msg: Message):
    return dict(id_in_group=msg.player_id_in_group, text=msg.text,)


def live_method(player: Player, data):
    group = player.group
    my_id = player.id_in_group

    msg_type = data['type']

    if msg_type == 'write':
        text = data['text']
        msg = Message.create(player=player, player_id_in_group=my_id, text=text, group=group)
        followers = get_follower_ids(player, including_myself=True)
        return {follower: dict(messages=[to_dict(msg)]) for follower in followers}

    broadcast = {}
    if player.round_number == 1:
        followers = []
        i_follow = []
        i_dont_follow = [p.id_in_group for p in group.get_players() if p.id_in_group not in i_follow]
        unfiltered_messages = Message.filter(group=group)
        # i see my own messages in my feed
        my_feed_authors = i_follow + [my_id]
        messages = [to_dict(m) for m in unfiltered_messages if m.player_id_in_group in my_feed_authors]
    else:
        followers = player.participant.followers
        i_follow = player.participant.i_follow
        i_dont_follow = player.participant.i_dont_follow
        my_feed_authors = player.participant.my_feed_authors
        messages = player.participant.messages

    if msg_type == 'toggle_follow':
        leader_id = data['id_in_group']
        leader = group.get_player_by_id(leader_id)
        subs = Subscription.filter(follower=player, leader=leader)
        if subs:
            [sub] = subs
            sub.delete()
        else:
            Subscription.create(
                leader=leader,
                leader_id_in_group=leader.id_in_group,
                follower=player,
                follower_id_in_group=my_id,
            )
        # notify the other person of the change to their followers
        broadcast[leader_id] = dict(followers=get_follower_ids(leader, including_myself=True))

        followers = get_follower_ids(player, including_myself=True)

        i_follow = [sub.leader_id_in_group for sub in Subscription.filter(follower=player)]
        i_dont_follow = [p.id_in_group for p in group.get_players() if p.id_in_group not in i_follow]
        unfiltered_messages = Message.filter(group=group)
        # i see my own messages in my feed
        my_feed_authors = i_follow + [my_id]
        messages = [to_dict(m) for m in unfiltered_messages if m.player_id_in_group in my_feed_authors]

    player.participant.followers = followers
    player.participant.i_follow = i_follow
    player.participant.i_dont_follow = i_dont_follow
    player.participant.my_feed_authors = my_feed_authors
    player.participant.messages = messages


    broadcast.update(
            {
                my_id: dict(
                    full_load=True,
                    followers=followers,
                    i_follow=i_follow,
                    i_dont_follow=i_dont_follow,
                    messages=messages,
                )
            }
        )

    return broadcast

# PAGES
class MyPage(Page):
    live_method = live_method

    @staticmethod
    def js_vars(player: Player):
        return dict(my_id=player.id_in_group)

    @staticmethod
    def is_displayed(player: Player):
        return player.round_number == 1


class MyPageRoundTwo(Page):
    live_method = live_method

    @staticmethod
    def js_vars(player: Player):
        return dict(my_id=player.id_in_group)

    @staticmethod
    def is_displayed(player: Player):
        return player.round_number > 1


class Results(Page):
    @staticmethod
    def is_displayed(player: Player):
        return player.round_number == C.NUM_ROUNDS


page_sequence = [MyPage, MyPageRoundTwo, Results]

#2 by Chris_oTree

Making it run across rounds adds complexity because ExtraModel should be linked to a round. Are you sure you need 2 rounds? What is the difference between the rounds?

#3 by Gabriela

Thank you for the reply, Chris. That I actually makes a lot of sense. So what's happening is that although the lists that display the connections initially are being loaded correctly, when creating new connections since the ExtraModel object is empty(?) it resets such connections. 

Yes, we need to run this through multiple rounds. What we want to do is add opinion questions in between and check how the network evolves as a result of sharing different opinions. If linking the ExtraModel from round to round is tricky, would creating it together with the connections history work? I'm thinking about something along these lines:  


broadcast = {}
    [ ... ]
    else:
        followers = player.participant.followers
        i_follow = player.participant.i_follow
        i_dont_follow = player.participant.i_dont_follow
        my_feed_authors = player.participant.my_feed_authors
        messages = player.participant.messages
        
        for leader in i_follow: 
            Subscription.create(
                leader=leader,
                leader_id_in_group=group.get_player_by_id(leader),
                follower=player,
                follower_id_in_group=my_id,
            )
            
        [ ... ]
        
Alternatively, how would one go about linking ExtraModels from round to round?

Thanks for your assistance. I appreciate! 

Best, 

Gabriela

#4 by Chris_oTree

I recommend to either use ExtraModel or participant fields, not both. Otherwise it gets confusing about where to get the data from.

I guess storing things in participant fields and session fields is the easiest way to go.

#5 by Gabriela

I'm not sure I understand the suggestion. The ExtraModel takes care of creating/destroying the connection, so what you're suggesting is that I don't use the ExtraModel at all? As the code is written now, it is needed to retrieve the list that is later stored in the participant fields. If I no longer use it then, I should create lists the old fashion way right?

#6 by Gabriela

UPDATE: 

I've solved the issue by creating the ExtraModel under an else, I think this logic is similar to what's done in the tictactoe game and works in my case. This way, I can call the history with participant vars but still handle the connections with an ExtraModel. 

def live_method(player: Player, data):
    group = player.group
    my_id = player.id_in_group

    msg_type = data['type']

    if msg_type == 'write':
        text = data['text']
        msg = Message.create(player=player, player_id_in_group=my_id, text=text, group=group)
        followers = get_follower_ids(player, including_myself=True)
        return {follower: dict(messages=[to_dict(msg)]) for follower in followers}

    broadcast = {}
    if player.round_number == 1:
        followers = []
        i_follow = []
        i_dont_follow = [p.id_in_group for p in group.get_players()]
        unfiltered_messages = Message.filter(group=group)
        my_feed_authors = i_follow + [my_id]
        messages = [to_dict(m) for m in unfiltered_messages if m.player_id_in_group in my_feed_authors]
    else:
        followers = player.participant.followers
        i_follow = player.participant.i_follow
        i_dont_follow = player.participant.i_dont_follow
        my_feed_authors = player.participant.my_feed_authors
        messages = player.participant.messages

    if msg_type == 'toggle_follow':
        leader_id = data['id_in_group']
        leader = group.get_player_by_id(leader_id)
        subs = Subscription.filter(follower=player, leader=leader)
        if subs:
            [sub] = subs
            sub.delete()
        else:
            Subscription.create(
                leader=leader,
                leader_id_in_group=leader.id_in_group,
                follower=player,
                follower_id_in_group=my_id,
            )
        # notify the other person of the change to their followers
        broadcast[leader_id] = dict(followers=get_follower_ids(leader, including_myself=True))

        followers = get_follower_ids(player, including_myself=True)
        i_follow = [sub.leader_id_in_group for sub in Subscription.filter(follower=player)]
        i_dont_follow = [p.id_in_group for p in group.get_players() if p.id_in_group not in i_follow]
        unfiltered_messages = Message.filter(group=group)
        my_feed_authors = i_follow + [my_id]
        messages = [to_dict(m) for m in unfiltered_messages if m.player_id_in_group in my_feed_authors]

    else:
        for temp in i_follow:
            Subscription.create(
                leader=group.get_player_by_id(temp),
                leader_id_in_group=group.get_player_by_id(temp).id_in_group,
                follower=player,
                follower_id_in_group=my_id,
            )

    player.participant.followers = followers
    player.participant.i_follow = i_follow
    player.participant.i_dont_follow = i_dont_follow
    player.participant.my_feed_authors = my_feed_authors
    player.participant.messages = messages

    broadcast.update(
        {
            my_id: dict(
                full_load=True,
                followers=followers,
                i_follow=i_follow,
                i_dont_follow=i_dont_follow,
                messages=messages,
            )
        }
    )

    return broadcast

Write a reply

Set forum username