10. Elementen combineren#

Er ontbreekt nog één cruciaal onderdeel van het spel: de mogelijkheid om elementen te combineren. Wanneer de speler een element card op een andere element card dropt, moeten we controleren of er een recept voor die combinatie bestaat. Als dat zo is, moeten we het nieuwe element tonen en de twee oorspronkelijke elementen verwijderen.

Om te beginnen voegen we een nieuwe variabele toe, waarin we de index van de element card gaan opslaan die de speler wil combineren met de element card die de speler op dat moment aan het slepen is. We noemen deze variabele other en zetten hem standaard op -1:

38# VARIABLES
39
40dragging = False
41dragged = {}
42other = -1

In de on_mouse_move() functie controleren we of de card die de speler aan het slepen is, contact maakt met een andere card in de workbench. Als dat zo is, slaan we de index van die card op in de other variabele:

120def on_mouse_move(pos):
121   global other
122   if dragging:
123      dragged['rect'].x = pos[0] - dragged['click_pos'][0]
124      dragged['rect'].y = pos[1] - dragged['click_pos'][1]
125      for index, card in enumerate(workbench):
126            if dragged['rect'].colliderect(card['rect']):
127               other = index
128               return
129      other = -1

De enumerate() functie is handig omdat we hiermee in de for loop zowel de index als de waarde van elk element in de workbench lijst kunnen krijgen. In regel 126 checken we mogelijke collisions tussen de dragged card en de cards in de workbench. Als er een collision is, slaan we de index van die card op in de other variabele en we verlaten de functie. Als de for loop helemaal is doorlopen en er waren geen collisions, dan zetten we other terug op -1.

Om aan de speler duidelijk te maken dat de card die hij aan het slepen is, een andere card raakt, kunnen we die card een andere kleur geven. We doen dit in de draw_workbench() functie:

166def draw_workbench():
167   for index, card in enumerate(workbench):
168      if index == other:
169            draw_element_card(card['id'], card['rect'].topleft, bgcolor='orange')
170      else:
171            draw_element_card(card['id'], card['rect'].topleft)

Ook hier gebruiken we de enumerate() functie om de index van de card te krijgen. Als de index gelijk is aan de other variabele, tekenen we de card met een oranje achtergrondkleur. Anders tekenen we de card met de standaard achtergrondkleur. Run de code om te zien of dit naar behoren werkt.

../_images/drag_and_drop_04.png

Tenslotte moeten we de combinatie van de twee cards verwerken. Dit doen we in de on_mouse_up() functie. Wanneer de speler een card dropt op een andere card, kunnen drie dingen gebeuren:

  • Er bestaat geen recept voor de combinatie van de twee elementen. In dat geval laten we de card terugspringen naar de oorspronkelijke positie.

  • Er bestaat een recept voor de combinatie van de twee elementen en het resultaat was nog niet ontdekt. In dat geval voegen we het nieuwe element toe aan de workbench en ook aan de inventory. We verwijderen de twee oorspronkelijke elementen uit de workbench.

  • Er bestaat een recept voor de combinatie van de twee elementen en het resultaat was al ontdekt. Er gebeurt hetzelfde als in het vorige geval, maar we voegen het nieuwe element niet toe aan de inventory, want het staat er al in.

Laten we eerst de functie get_recipe() toevoegen, die de combinatie van twee elementen controleert en het resultaat teruggeeft als het recept bestaat. Deze functie is exact hetzelfde als de get_recipe() functie uit de tekstversie van het spel:

90def get_recipe(ingredient1, ingredient2):
91   ingredients = sorted([ingredient1, ingredient2])
92   if ingredients[0] in recipes:
93      if ingredients[1] in recipes[ingredients[0]]:
94         return recipes[ingredients[0]][ingredients[1]]
95   return None

Om ervoor te zorgen dat een card kan terugspringen naar de oorspronkelijke positie als er geen recept bestaat, moeten we de oorspronkelijke positie van de card opslaan voordat de speler begint met slepen. Dit doen we in de on_mouse_down() functie:

 99def on_mouse_down(pos, button):
100   global dragged, dragging
101   if pos[0] < inventory_width:
102      # Clicked in inventory
103      for card in inventory:
104            r = card['rect']
105            if r.collidepoint(pos):
106               dragged = {
107                  'id' : card['id'],
108                  'rect' : r.copy(),
109                  'click_pos' : (pos[0] - r.x, pos[1] - r.y),
110                  'old_rect' : r.copy()
111               }
112               dragging = True
113               return
114   else:
115      # Clicked in workbench
116      for card in reversed(workbench):
117            r = card['rect']
118            if r.collidepoint(pos):
119               dragged = {
120                  'id' : card['id'],
121                  'rect' : r,
122                  'click_pos' : (pos[0] - r.x, pos[1] - r.y),
123                  'old_rect' : r.copy()
124               }
125               workbench.remove(card)
126               dragging = True
127               return

Aan de on_mouse_up() functie voegen we de volgende code toe:

140def on_mouse_up():
141   global dragging, other
142   if dragging:
143      dragging = False
144      r = dragged['rect']
145      if workbench_rect.contains(r):
146            if other != -1:
147               result = get_recipe(dragged['id'], workbench[other]['id'])
148               if result != None:
149                  # recipe available
150                  if result not in [value for dict in inventory for key, value in dict.items() if key == 'id']:
151                        # result not yet in discoveries
152                        add_element_to_list(result, inventory)
153                  add_element_to_list(result, workbench, r)
154                  workbench.pop(other)
155                  dragged.clear()
156                  other = -1
157                  return
158               else:
159                  r = dragged['old_rect']
160            add_element_to_list(dragged['id'], workbench, r)
161      dragged.clear()
162      other = -1

In regel 146 controleren we of de speler de card loslaat op een andere card.
Als dat zo is, halen we in regel 147 het recept op met de get_recipe() functie.
Als een recept bestaat, controleren we in regel 150 of het resultaat nog niet in de inventory voorkomt. Dit is een ingewikkelde regel, omdat de inventory een lijst van dictionaries is, en we van elk 'id' veld in die dictionaries moeten checken of de waarde overeenkomt met result.
Vevolgens voegen we eventueel het resultaat toe aan de inventory en daarna ook aan de workbench (op de positie van de gesleepte card). We verwijderen het other element uit de workbench en we wissen de dragged en other variabelen en we verlaten de functie.
Als er geen recept bestaat, zetten we de card terug naar de oorspronkelijke positie. Dit doen we door de old_rect van de card te gebruiken, die we eerder hebben opgeslagen in de on_mouse_down() functie.
Als de card helemaal niet op een andere card is losgelaten, voegen we de card toe aan de workbench zoals dat eerder ook al het geval was.
In regel 162 resetten we other naar -1. Als we dit niet doen, zou een eventuele ‘other’ card nog steeds oranje worden getekend, ook als de gesleepte card is teruggesprongen naar de oude positie (probeer maar).

En hiermee hebben we een speelbare versie van het spel! Je kunt nu elementen combineren en nieuwe elementen ontdekken.

../_images/drag_and_drop_05.png