IsaacPang/MarketRoyale

BUG: Issue with concurrent commands

Closed this issue · 1 comments

Describe the bug
If 2 players are at the same node, and they decide to buy out an item, there isn't actually a catch for which player will receive the items. The Game.py will update it's own dictionary to keep track of our inventory, but we will not be told what amount of products we will receive as a player.

Code Snippets

Notice from below, Market defines the sell as a minimum of the amount it has and the amount requested. However, in the buy command issued in game, there is no player update that references the player object, only the dictionary self.players that stores a copy of the player information to keep track of the players so they don't cheat.

Market.py

    def sell(self, product, amount):
        """Sell amount up to as much as I have and return the product and amount sold & cost.
        """
        assert(product in PRODUCTS)
        assert(type(amount) is int)
        if amount < 0:
            return (product, 0, 0)
        a = min(amount, self.amounts[product])
        self.amounts[product] -= a
        return (product, a, self.prices[product] * a)

Game.py

                elif cmd == Command.BUY:
                    if p_id not in self.have_researched[p_info[INFO_LOC]]:
                        msg.append(MSG_NO_RESEARCH)
                    else:
                        if p_info[INFO_INV][INV_GOLD] < 0:
                            msg.append("Not enough gold to buy anything.")
                        else:
                            assert(len(data) == 2)
                            data = list(data)
                            prod,am,cost = market.sell(*data)
                            p_info[INFO_INV][prod] += am
                            p_info[INFO_INV][INV_GOLD] -= cost
                            msg.append("Bought {} {}".format(am, prod))
temp = list(self.players.items())
            random.shuffle(temp)
            for p_id,p_info in temp:

Sample output from Game.players

g.players
Out[9]: 
{1: {0: 'F',
  1: 0,
  2: {'gold': 24765.0, 'goal': {'Food': 94, 'Electronics': 21, 'Social': 97, 'Hardware': 1}, 'map': <Map.Map object at 0x0000016707938EC8>, 'turn': 0, 'researched': {}, 'rumours': {}, 'inventory': {}, 'score': 0, 'goal_acheived': False, 'visited_node': defaultdict(<class 'int'>, {}), 'loc': ''},
  3: {'Food': 0,
   'Electronics': 0,
   'Social': 0,
   'Hardware': 0,
   'Gold': 24765.0},
  4: [],
  5: {'Food': 94, 'Electronics': 21, 'Social': 97, 'Hardware': 1}},
 2: {0: 'C',
  1: 0,
  2: {'gold': 24765.0, 'goal': {'Food': 73, 'Electronics': 10, 'Social': 179, 'Hardware': 3}, 'map': <Map.Map object at 0x00000167079567C8>, 'turn': 0, 'researched': {}, 'rumours': {}, 'inventory': {}, 'score': 0, 'goal_acheived': False, 'visited_node': defaultdict(<class 'int'>, {}), 'loc': ''},
  3: {'Food': 0,
   'Electronics': 0,
   'Social': 0,
   'Hardware': 0,
   'Gold': 24765.0},
  4: [],
  5: {'Food': 73, 'Electronics': 10, 'Social': 179, 'Hardware': 3}}}
g.num_players
Out[10]: 2

There isn't actually a bug in the game. Based on this code, and the ones before this, the "this_market" price is always updated after the player before this does. The amount received will always be min(amount requested, amount in market).

            for p_id,p_info in temp:

                if self.verbose:
                    self.map.render_map()
                    self.map.pretty_print_map()

                msg = []

                if p_info[INFO_INV][INV_GOLD] < 0:
                    i = round(-self.interest * p_info[INFO_INV][INV_GOLD])
                    msg.append("Interest of {} charged.".format(i))
                    p_info[INFO_INV][INV_GOLD] -= i

                if self.map.outside_circle(p_info[INFO_LOC]):
                    msg.append("Outside circle charge {}".format(OUTSIDE_CIRCLE_PENALTY))
                    p_info[INFO_INV][INV_GOLD] -= OUTSIDE_CIRCLE_PENALTY
                    
                other_info = {}
                if p_info[INFO_N] == 0:
                    other_info = self.get_prices_from_other_players(p_id)
                p_info[INFO_N] += 1

                market = self.markets[p_info[INFO_LOC]]

                if p_id in self.have_researched[p_info[INFO_LOC]]:
                    this_market = copy.deepcopy(market.get_price_amount())
                else:
                    this_market = {}

                try:
                    cmd,data = p_info[INFO_OBJ].take_turn(p_info[INFO_LOC], this_market, copy.deepcopy(other_info), bnodes, gnodes)