-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfinal_model.py
1142 lines (1054 loc) · 55.8 KB
/
final_model.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
"""
Created on Thu Oct 15 09:26:33 2020
As part of Programming for Social Science MSc/PhD course 2020 at the
School of Geography, University of Leeds
@author: Md Mushtahid Salam
email: mushtahid@gmail.com
This project is licensed under the GNU General Public License v3.0 or any later version
Project repository: https://github.com/mushtahid/GEOG5995-Practicals
"""
# Import modules
# import sys # To print the output in a seperate text file
import final_agent_framework as af # Import the agentframework
import tkinter # To use for the GUI
# Matplotlib for plotting/animating environment and animals.
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import matplotlib.animation
import random # To randomize things!
# random.seed(10) # Uncomment to debug the model
import csv # To read and write data
import requests # To scrape web data
import bs4 # To scrape web data
import time # To calculate the process time for each iteration and total model
# Print out the output in a text file.
# Better for debuggin the code as it
# can be searched easily using printed texts/keywords using NotePad++ or
# other similar text editors!
# WARNING:
# Recommended to enable the stdout code only if you want to debug! Make sure
# to uncomment sys module and also look at the end of the code to uncomment
# closing stdout.
# Otherwise if the file is run from CMD, it will not display any prompts to
# run the code! However, if the code is run from Spyder it will work
# with stdout.
# See the end as well for closing the stdout.
# stdoutOrigin = sys.stdout
# sys.stdout = open("log.txt", "w")
# Scrape web data to find x and y values for the sheep!
# Perhaps short url should not be used, as it may decrease efficiency?
r = requests.get('http://bit.ly/GeogLeedsAFData')
content = r.text
soup = bs4.BeautifulSoup(content, 'html.parser')
td_ys = soup.find_all(attrs={"class" : "y"})
td_xs = soup.find_all(attrs={"class" : "x"})
# print(td_ys)
# print(td_xs)
# Set up plot size and axes
fig = plt.figure(figsize=(7, 7))
ax = fig.add_axes([0, 0, 1, 1])
# ax.set_autoscale_on(True)
# Modifiable variables!
# Prompt if want to modify:
modify = input("- Do you want to alter the modifiable variables? Enter "\
"'y' (lowercase and without ' ') if YES (There are 11 "\
"variables that you can modify. You will still "\
"be able to run with default values for "\
"individual variables if you want). "\
"If NO, enter any other value to run the model "\
"with default values: ")
correct_y = "y"
if modify == correct_y:
# Number of sheep
n_sh = input('- Enter number of sheep. (Or press enter to use default'\
' value 15): ')
try:
no_sheep = int(n_sh)
assert no_sheep >=0
except:
no_sheep = 15
print('Invalid characters/No number entered! Model will run with '\
'15 sheep.')
# Number of wovles
n_wl = input('- Enter number of wolves. (Or press enter to use default '\
'value 5): ')
try:
no_wolves = int(n_wl)
assert no_wolves >= 0
except:
no_wolves = 5
print('Invalid characters/No number entered! Model will run with '\
'5 wolves.')
# Input number of iterations
n_it = input('- Enter number of iterations. The model will stop once '\
'all iterations are completed OR if all sheep have been '\
'eaten by the wolves! You can change the number of '\
'iterations. REMEMBER: Python starts counting from 0! So, '\
'if you enter 99, it will run up to 100 iterations and if '\
'you enter 100 it will run upto 101 iterations. '\
'(Or press enter to use default value 499, '\
'i.e 500 iterations! ): ')
try:
no_iterations = int(n_it)
assert no_iterations > 0
except:
no_iterations = 499 # Number of times the animation will run
print('Invalid characters/No number entered! Model will run with '\
'default value of 499, i.e, 500 iterations.')
# Input proximity
prox = input('- Enter proximity value. Proximity refers to the range of '\
'vision or distance within which WOLVES can observe and '\
'notice other animals and determine the closest one. '\
'(Or press enter to use default value 50): ')
try:
proximity = int(prox)
assert proximity > 0
except:
proximity = 50 # the range of vision of the wolves
print('Invalid characters/No number entered! Model will run with '\
'default of wolf proximity of 50.')
# Input the denometer for sheep minimum distance.
smd = input('- Enter the sheep proximity denominator. By default, the '\
'proximity of sheep is half of that of wolves. '\
'i.e., proxmity/2. This is because sheep is a prey! '\
'You can change the denominator to alter the '\
'sheep proximity. (Or press enter to use default '\
'denominator value 2): ')
try:
smd_denometer = int(smd)
assert smd_denometer > 0
except:
smd_denometer = 2
print('Invalid characters/No number entered! Model will run with '\
'default sheep proximity denominator of 2.')
# Input action_dist
actd = input('- Enter the action distance within which the animals will '\
'interact, such as breed, fight and so on. Same for sheep '\
'and wolves. (Or press enter to use default value 5): ')
try:
action_dist = int(actd)
assert action_dist > 0
except:
action_dist = 5 # Proximity within which animals can interact,eg breed.
print('Invalid characters/No number entered! Model will run with '\
'default action distance of 5.')
# Input probability
prob = input('- Enter sheep breeding probability. Probability represents '\
'the chance of breeding for sheep, e.g., '\
'if the probability>0.5, the sheep will breed '\
'successfully, i.e., if random value>0.5. Otherwise '\
'breeding attempt will fail. Note: for breeding to be '\
'successfull, randome value has to be MORE than the '\
'probability value (random value>probability). '\
'You can change it. (Or press enter to use default '\
'value 0.5): ')
try:
probability = float(prob)
assert 0 < probability <= 1.0
except:
probability = 0.50
print('Invalid characters/No number entered! Model will run with '\
'default sheep probability value of 0.5.')
# Input probability for wolves
wolf_prob = input("- The probability of breeding for wolves by default "\
f"is the same as that of sheep probability: {probability}. "\
"For wolves, this also represents the chance of winning a "\
"fight with other wolves. It also determines fraction of "\
"the losing wolf's store the winning wolf will win. "\
"You can change it. Note: for breeding/fight "\
"to be successfull, random value has to be MORE than "\
"the probability value (i.e., random>probability). "\
"You can chagne it. (Or press enter to use sheep "\
f"probability value ({probability}): ")
try:
bf_e = float(wolf_prob)
assert 0 < bf_e <= 1.0
except:
bf_e = probability
print('Invalid characters/No number entered! Model will run with '\
'default wolf probability that is the same as sheep '\
f'probability: {probability}.')
# Input the sheep minimum energy
sme = input("- Sheep need a minimum energy/store to breed. "\
"You can change it. (Or press enter to use default store "\
"value 600): ")
try:
s_min_energy = int(sme)
assert s_min_energy > 0
except:
s_min_energy = 600 # Set minimum energy value for sheep to breed.
print('Invalid characters/No number entered! Model will run with '\
'default sheep minimum energy/store needed for action of 600.')
# Input the wolf minimum energy
wme = input("- Wolves also need a minimum energy/store to breed. "\
"You can change it. (Or press enter to use default store "\
"value 500): ")
try:
min_energy = int(wme)
assert min_energy > 0
except:
min_energy = 500 # Set wolves minimum store value for breeding.
print('Invalid characters/No number entered! Model will run with '\
'default wolf minimum energy/store needed for action of 500.')
# Input the wolf high store multiplier
hstm = input("- Wolves have the extra ability to increase their "\
"proximity once they have a store value above a "\
"certain threshold. "\
"For example, the default is double that of minimum "\
"energy, i.e., if store>min_energy*2, then proximity "\
"is also multiplied by 2!. You can change this Multiplier. "\
"It can be a float!"\
"If you don't want wolves to become 'super wolves', then "\
"input 1! (Or press enter to run with default "\
"multiplier of 2): ")
try:
high_store_mp = float(hstm)
assert high_store_mp > 0
except:
# Hight store multiplier: to increase the proximity of wolves if they
# have a certain amount energy more than the minimum energy
high_store_mp = 2
print('Invalid characters/No number entered! Model will run with '\
'default multiplier value of 2.')
# Print initial settings!
print('____Model will run with the following settings____')
print(f"Number of sheep: {no_sheep}")
print(f"Number of wolves: {no_wolves}")
print(f"Number of iterations: {no_iterations+1}")
print(f"Wolf proximity: {proximity}")
print(f"Sheep proximity denominator: {smd_denometer} and so sheep "\
f"proximity: {proximity/smd_denometer}")
print(f"Action distance: {action_dist}")
print(f"Sheep probability: {probability}")
print(f"Wolf probability: {bf_e}")
print(f"Sheep minimum energy: {s_min_energy}")
print(f"Wolf minimum energy: {min_energy}")
print(f"Wolf high store multiplier: {high_store_mp}")
print('') # Add blank space
else:
no_sheep = 15
no_wolves = 5
no_iterations = 499
proximity = 50
smd_denometer = 2
action_dist = 5
probability = 0.50
bf_e = probability
s_min_energy = 600
min_energy = 500
high_store_mp = 2
# Print default settings!
print('____Model will run with default variables____')
print(f"Number of sheep: {no_sheep}")
print(f"Number of wolves: {no_wolves}")
print(f"Number of iterations: {no_iterations+1}")
print(f"Wolf proximity: {proximity}")
print(f"Sheep proximity denominator: {smd_denometer} and so sheep "\
f"proximity: {proximity/smd_denometer}")
print(f"Action distance: {action_dist}")
print(f"Sheep probability: {probability}")
print(f"Wolf probability: {bf_e}")
print(f"Sheep minimum energy: {s_min_energy}")
print(f"Wolf minimum energy: {min_energy}")
print(f"Wolf high store multiplier: {high_store_mp}")
print('') # Add blank space
# Variables that should not be altered.
environment = [] # Set environment before animals.
sheep = [] # List of sheep.
wolves = [] # List of wolves.
total_time = 0 # Set initial total process time as zero.
sheep_min_dist = proximity/smd_denometer # Sheep minimum distance,
total_sheep_store = 0.0 # For calculating the toal sheep store as float.
t_sheep_store_list = [] # For use in writing the total sheep store in a file.
total_wolves_store = 0.0 # For calculating the toal wovles store as float.
t_wolves_store_list = [] # For use in writing the total wolves store in a file.
it_no = 0 # No counting the number of iterations.
carry_on = True # To stop model when all sheep are eaten.
# Read environment data from a text file.
with open('in.txt', newline='') as f:
env_reader = csv.reader(f, quoting=csv.QUOTE_NONNUMERIC)
for row in env_reader:
rowlist = []
for value in row:
rowlist.append(value)
environment.append(rowlist)
# Initialise sheep
for i in range(no_sheep):
# Multiply x and y with 3 to make them more spread out. However, I did not
# as the sheep should stay in a herd. So makes sense for them to appear
# closer within a grid of 100X100 as assigned by the webdata!
y = int(td_ys[i].text)
x = int(td_xs[i].text)
sheep.append(af.Sheep(environment, sheep, wolves, y, x))
# print('Initial number of sheep: ', len(sheep))
# Initialise wolves
for i in range(no_wolves):
y = None #int(td_ys[-i].text)*3 #interesting!
x = None #int(td_xs[-i].text)*3
wolves.append(af.Wolf(environment, sheep, wolves, y, x))
# print('Initial number of wolves:', len(wolves))
# Set up update/frames for animation
def update(frame_number):
"""Updates the plot with the new parameters"""
# Start the timer
start = time.process_time()
# Clear the plot from previous iteration!
fig.clear()
# Call global it_no to update it at each iteration.
global it_no
it_no += 1
# See towards the end of wolves.
global total_time
global carry_on
print('') # Add a space between two printouts!
print(f"*************** ITERATION NUMBER {it_no} BEGINS ************")
# Randomly shuffle the order of initialisation for the animals
# at each iteration so that everyone gets a fair shot at
# whatever their action is (eating/breeding/mating/fighting etc.)
# Comment out the random.shuffle for debugging.
random.shuffle(sheep)
random.shuffle(wolves)
# S.1
# sheep_count tracks the current sheep (i) via it's index value.
sheep_count = -1
# sheep_index tracks the other sheep (j) with which the current sheep
# interacts with via their index value. Used to prevent the current sheep
# from interacting with itself and prevent interaction of the same pair
# in the same iteration.
sheep_index = 0
# slice [:] is used in for loop because sheep may breed
# with each other.
for i in sheep[:]:
sheep_count += 1 # Increase by 1 to assign the i sheep it's index.
sheep_index += 1 # Increase by 1 to use as the start index
# when comapring with other sheep.
# s_min_dist (minimum distance) for sheep is sheep_min_dist, which is
# half of proximity as sheep is a prey! Wolves have higer field of
# vision, so they have access to full proximity distance!
s_min_dist = sheep_min_dist
# closest_wolf targets and tracks the closest wolf to run away from!
closest_wolf = None
# print(sheep_count)
# print(sheep_index)
# print(s_min_dist)
# print(closest_wolf)
print('') # Add blank space
print(f"-> ----- {sheep_count}-Sheep initialised with ({i}) ------")
# Actions for sheep: check for close wolves (CW) within s_min_dist.
# If found, run away. While running away it does not eat.
# If no CW. Looks for close sheep (CS) if it has s_min_energy to move
# closer/breed/share. While moving closer it eats as well. If no CS
# found but has s_min_energyit moves randomly and eats. If it does not
# have s_min_energy then it moves randomly and eats.
# S.1.1
# For j loop below, range(len(wolves)) is used. Slice of wolves
# is not used for looping with sheep as the wolves will not mate with
# sheep to increase the number of wolves. range(no_wolves) is not used
# as no_wolves represents inital wolf number and the wolves may breed
# (see further below_ to increase their numbers, and so would fail
# the model if used.
for j in range(len(wolves)):
# Print below tests if current sheep is looping with all wolves!
print(f"--> {sheep_count}-Sheep looping with {j}-Wolf.")
# Calculate the distance between itself and all wolves.
# distance = af.Animal.dist_animals(i, wolves[j]) # This works too.
distance = i.dist_animals(wolves[j])
# print(f"--> distance={distance} for {i}-Sheep ({sheep[i]}) with"
# f"{j}-Wolf ({wolves[j]})")
# S.1.1.1
# If wolf(ves) detected within s_min_dist, find the
# closest wolf (CW).
if distance < s_min_dist: # d<md+ as keyword for debugging.
print(f"--->d<md+...{sheep_count}-Sheep noticed "
f"closest {j}-Wolf ...") # This may be commented off!
s_min_dist = distance # Assign the new s_min_dist
closest_wolf = wolves[j] # Assign the new CW
print(f"---> {sheep_count}-Sheep ({i}) detected {j}-Wolf "
f"({wolves[j]}) within d={s_min_dist}.")
# S.1.2
# If CW found, try to run away!
if closest_wolf != None:
# The algorithm may be improved here based on store
# and probability
print("-> CW FOUND.")
print(f"--> Before {sheep_count}-Sheep ({i}) tries to run away "
f"from CW: ({closest_wolf}).")
# Call the run_from_cw method from Sheep class to run away from CW
i.run_from_cw(closest_wolf) # Works!
print(f"--> After {sheep_count}-Sheep ({i}) tried to run away "
f"from CW ({closest_wolf}).")
print(f"___________ {sheep_count}-Sheep tried to run away "
f"from CW ___________ ")
# S.1.3
# If no CW found:
elif closest_wolf == None:
print(f"-> NO CW FOUND. {sheep_count}-Sheep will try to either, "
f"(if energy allows: find CS to move closer+eat "
f"OR breed/share) "
f"OR (move/eat normally if NO CS or low energy).")
breed = False # To check if the sheep bred successfully.
fail_breed = False # To check if breeding failed.
share = False # To check if resources were shared.
closest_sheep = None # To target and track the closest sheep (CS)
sheep2_count = -1 # To assign index to the other sheep.
# S.1.3.1
# if store > s_min_energy, it tries to find CS:
if i.store > s_min_energy:
print(f"->{sheep_count}-Sheep has Store > Min_ernergy"
f"({s_min_energy}) ({i}).")
# S.1.3.1.1
# Try to find CS. Slice used as breeding may take place.
# sheep_index is used as the start index to prevent
# comparison between same sheep/same pair.
for j in sheep[sheep_index:]:
# Increase the sheep2_count by 1 to assign the j sheep
# it's index value.
sheep2_count += 1
# Print below tests if looping with i+1 sheep!
# eg., if current sheep is no-6, and there are total
# 7 sheep in current iterations, it should loop with
# only 1 sheep. If it's the 7th sheep it loops with none.
print(f"--> {sheep_count}-Sheep looping with "
f"{sheep2_count}-Sheep.")
# Calculate the distance between itself and other sheep.
# distance = af.Animal.dist_animals(i, j) # This woks too.
distance = i.dist_animals(j)
# S.1.3.1.1.a
# If other sheep is within minimum distance but beyond
# action distance, track it.
if action_dist < distance < s_min_dist:
print('----> (AD < D < MD).')
s_min_dist = distance # Assign the new s_min_dist.
closest_sheep = j # Assign the new CS.
print(f"----> Min distance={s_min_dist} for "
f"{sheep_count}-Sheep ({i}) with "
f"{sheep2_count}-Sheep ({j}).")
# S.1.3.1.1.b
# If other sheep is within actions distance: ACTION!
elif distance <= action_dist:
print(f"----> D <=AD. Sheep Breeding/Sharing "
f"proximity of {distance}(<={action_dist}) "
f"entered by CS {sheep_count}-Sheep ({i}) with "
f"CS {sheep2_count}-Sheep ({j}).")
# S.1.3.1.1.b.1
# CS also must have store>s_min_energy to have a
# chance of breeding.
# Sincec CS also has store>s_min_energy, try breeding.
if j.store > s_min_energy:
print(f"----->{sheep_count}-Sheep ({i}) and CS "
f"{sheep2_count}-Sheep ({j} have "
f"> Min_energy ({s_min_energy}) store. "
f"So they will try breeding if "
f"p > {probability}")
# Call the breed_cost method from Parent class
# to calculate the new store value after breeding
# attemp (regardless of successful/failed)
breeding_cost = i.breed_cost(j) #This works!
# S.1.3.1.1.b.1.1
# if p>probability, breeds successfully.
if random.random() > probability:
print(f"------>1.A. Random>{probability}."
f" So the sheep will breed.")
print(f"------> {sheep_count}-Sheep ({i}) "
f"before breeding with CS "
f"{sheep2_count}-Sheep ({j}).")
# Set new store values
i.store = breeding_cost
j.store = breeding_cost
# Append new sheep with +10 y,x of i sheep,
# so that it is placed closer when appended.
sheep.append(af.Sheep(environment, sheep,\
wolves, y=(i.y+10), x=(i.x+10)))
breed = True # Set breed as true.
print(f"------> Sheep Breed = {breed}")
print(f"------> {sheep_count}-Sheep ({i}) "
f"after breeding with CS "
f"{sheep2_count}-Sheep ({j}).")
print(f"_____________ {sheep_count}-Sheep "
f"Mated SUCCESSFULLY with "
f"CS {sheep2_count}-Sheep ____________")
# Break away from the loop and go to if
# statement below to move onto next sheep.
# This is because the sheep has already
# interacted with a CS. So should not repeat
# if another CS present within action_dist.
break
# S.1.3.1.1.b.1.2
# if p<probability, breeding fails.
else:
print(f"------>1.B Random <{probability}."
f" So the breeding will fail.")
print(f"------> {sheep_count}-Sheep ({i}) "
f"before failed breeding with "
f"CS {sheep2_count}-Sheep ({j})")
# Breeding attempted. Just was not successful!
# So energy deduction will be same as
# successful breeding.
i.store = breeding_cost
j.store = breeding_cost
# Set fail_breed as true.
fail_breed = True
print(f"------> Failed Sheep Breeding = "
f"{fail_breed}")
print(f"------> {sheep_count}-Sheep ({i}) "
f"after failed breeding with "
f"CS {sheep2_count}-Sheep ({j}).")
print(f"_____________ {sheep_count}-Sheep "
f"Fail Mated with "
f"CS {sheep2_count}-Sheep ____________")
# Break away from the loop and go to if
# statement below to move onto next sheep.
# This is because the sheep has already
# interacted with a CS. So should not repeat
# if another CS present within action_dist.
break
# S.1.3.1.1.b.2
# CS also must have store > s_min_energy to have a
# chance of breeding.
# Since CS does not have >s_min_energy,
# Sheep i and CS j will share store to become average.
# They won't fight unlike wolves, as their main
# goal is survival.
else:
print(f"-----> {sheep_count}-Sheep ({i}) and "
f"CS {sheep2_count}-Sheep ({j} will not "
f"mate as CS {sheep2_count}-Sheep store "
f"<{s_min_energy}. They will share resource"
f" to become average.")
# Calculate average store value and assign
# The average may be converted into a method
# as well.
average = (i.store + j.store)/2
# Set store values as average
i.store = average
j.store = average
# Set share as true.
share = True
print(f"-----> Shared resources = {share}")
print(f"-----> {sheep_count}-Sheep ({i}) and "
f"CS {sheep2_count}-Sheep ({j}) "
f"after sharing resources!")
print(f"_____________ {sheep_count}-Sheep and "
f"CS {sheep2_count}-Sheep shared "
f"resources ____________")
# Break away from the loop and go to if
# statement below to move onto next sheep.
# This is because the sheep has already
# interacted with a CS. So should not repeat
# if another CS present within action_dist.
break
# S.1.3.1.2
# when ACTION done, continue to next sheep!
if breed or fail_breed or share == True:
print(f"_______ Breeding={breed}, "
f"Failed breeding={fail_breed}, "
f"Share={share} for {sheep_count}-Sheep __________")
# Continue to next sheep as this sheep has
# already interacted with a CS within action_dist.
continue
# S.1.3.1.3
# If no sheep within action_dist but CS found within
# s_min_dist, track and get closer to it while eating.
# This allows to get within action distance and at the same
# time, allows the sheep to stay closer like in a herd!
elif closest_sheep != None:
print("->CS Found!")
print(f"-> {sheep_count}-Sheep ({i}) before moving "
f"closer (while eating) to "
f"CS-Sheep: ({closest_sheep}).")
# Call the run_to_closest_animal method from Parent class,
# eat method from Sheep class.
i.run_to_closest_animal(closest_sheep) # Works!
i.eat() # Eat while moving like in a herd!
print(f"-> {sheep_count}-Wolf ({i}) after moving closer "
f"(while eating) to CS-Sheep: ({closest_sheep}).")
print(f"___________ {sheep_count}-Sheep moved "
f"closer CS___________" )
# S.1.3.1.4
# If No CS found, but has store>s_min_energy, moves and eats.
else:
print("-> NO CS Found.")
print(f"-> {sheep_count}-Sheep ({i}) before normally "
f"moving and eating.")
# Call the move and eat methods.
i.move()
i.eat()
print(f"-> {sheep_count}-Sheep ({i}) after normally "
f"moving and eating.")
print(f"...e1________ {sheep_count}-Sheep moved "
f"normally and ate ___________")
# S.1.3.2
# If store < s_min_energy, move and eat
else:
print(f"->{sheep_count}-Sheep has Store < "
f"Min_ernergy({s_min_energy}) ({i}). "
f"So moves and eat")
print(f"-->{sheep_count}-Sheep ({i}) before normal moving "
f"and eating")
# Call the move and eat methods.
i.move()
i.eat()
print(f"-->{sheep_count}-Sheep ({i}) after normal moving "
f"and eating")
print(f"___e2________ {sheep_count}-Sheep moved normally "
f"and ate ___________")
# # Get list of store values for sheep
# for i in range(len(sheep)):
# # print(i, 'After', 'Store: ', sheep[i].store)
# # Adds store value to store_list
# store_list.append(sheep[i].store)
# # print(store_list)
# # print(store_list)
# # print(min(store_list))
# S.1.4
# The print below adds a space between two sheep printouts
print('')
print('________________Sheep Cycle Ends_________________')
print('________________Wolf Cycle Begins________________')
# W.1
# wolf_count: to assign the current wolf it's index value.
wolf_count = -1
# wolf_index used to prevent comparison between same wolf/pair,
# used in j wolf slice as stratin index.
wolf_index = 0
for i in wolves[:]:
wolf_count += 1 # Assign the current wolf it's index value.
wolf_index += 1 # Increase by 1 to prevent comparsion between same w/p.
# W.1.1
# The if conditon below: If wolf has > (high_store_mp) the minimum
# energy (store), its vision improves, meaning its proximity is
# multiplied by high_store_mp as wll.
if i.store > min_energy*high_store_mp:
min_dist = proximity*high_store_mp
else:
min_dist = proximity
print('') # Add space between two wolves printouts.
print(f"---------{wolf_count}-Wolf initialised with ({i}) --------")
# Actions for the current wolf:
# 1. If (current wolf store <=min_energy) OR (store>min_energy AND
# p between bf_e/2 and <bf_e (which if bf_e not changed means,
# p is between 0.25 and <0.50)), then try find closest sheep (CS) to
# move closer to or if within action distance then eat. If no CS
# found, then just move randomly.
# 2. Else if the (current wolf store >min_energy AND p >= bf_e)
# (i.e., 0.5 if unchanged), it tries to find closest_wolf (CW) to move
# closer to, or if within action distance: breeds or fights. If no
# CW found, moves randomly.
# 3. Else if (current wolf store >min_energy AND p<bf_e/2) (i.e.,
# <0.25 if bf_e unchanged, the current wolf moves randomly.)
# W.1.2
# 1. If (current wolf store <=min_energy) OR (store>min_energy AND
# p between bf_e/2 and <bf_e (which if bf_e not changed means,
# p is between 0.25 and <0.50)), then try find closest sheep (CS) to
# move closer to or if within action distance then eat. If no CS
# found, then just move randomly.
# #Check if (current wolf store <=min_energy) OR (store>min_energy AND
# #p between bf_e/2 and <bf_e)
if i.store <= min_energy or (i.store > min_energy and\
((bf_e/2) <= random.random() < bf_e)):
if i.store <= min_energy:
print(f"--> {wolf_count}-Wolf has <=Min_energy({min_energy}) "
f"store ({i}). Tries to find food (sheep).")
else:
print(f"--> {wolf_count}-Wolf has >Min_energy({min_energy}) "
f"store ({i}) and {bf_e/2} <= random.random() < {bf_e}."
f" Tries to find food (sheep).")
sheep_count = -1 # To assign sheep its index value.
closest_sheep = None # To assign and track closest sheep (CS)
eaten = False # Set eat as false.
# W.1.2.1
# Loop through sheep list to find CS.
# Slice is used for sheep list as sheep might get eaten by wolf.
# No starting index is used in slice as each wolf should go
# through each sheep.
for j in sheep[:]:
sheep_count += 1 # Assign sheep its index value.
print(f"---> {wolf_count}-Wolf looping with "
f"{sheep_count}-Sheep")
# Check distance between current wolf and sheep
distance = af.Animal.dist_animals(i, j)
# print(f"distance={distance} between {i}-Wolf ({wolves[i]}) "
# f"and {sheep_count}-Sheep ({j})")
# W.1.2.1.A
# If wolf find a CS within action distance: eats it!
if distance <= action_dist:
# Eats the first CS sheep at action distance.
# So should not loop through the other ones.
print(f"----> CS within {distance} distance which is "
f"<= action distance ({action_dist}).")
print(f"----> {wolf_count}-Wolf ({i}) before eating "
f"CS {sheep_count}-Sheep ({j})")
# Call the eat method from Wolf class.
i.eat(j) # Works!
print(f"----> {wolf_count}-Wolf ({i}) after eating "
f"CS {sheep_count}-Sheep ({j}).")
eaten = True # Set eat as true
# Eaten, so break (don't eat other CS within AD and
# go to if below (and continue)
break
# W.1.2.1.B
# If CS within min_dist but beyond action distance, assign
# CS with closest_sheep and track it (move closer)
elif action_dist < distance < min_dist:
print(f"----> action_dist({action_dist}) < "
f"distance({distance}) < min_dist({min_dist}).")
# Set new minimum distance &
# update if even closer sheep found
min_dist = distance
# Set new CS & update if closer sheep found
closest_sheep = j
print(f"----> Min Distance = {min_dist} for "
f"{wolf_count}-Wolf ({i}) with "
f"{sheep_count}-Sheep ({j}).")
# W.1.2.2
# Comes here from the break above if eaten = True.
# Continue to next sheep.
if eaten == True:
print(f"___________ Eaten={eaten}. {wolf_count}-Wolf ate "
f"CS {sheep_count}-Sheep__________")
# Move on to next wolf,as this wolf has already
# already eaten a CS within action_dist, and so
# should not eat another CS if within action_dist
continue
# W.1.2.3
# if CS found (but beyond action distance) within minimum
# distance, chase it!
elif closest_sheep != None:
print("-> CS Found!")
print(f"--> {wolf_count}-Wolf ({i}) before moving closer to "
f"CS ({closest_sheep }).")
# Call the run_to_closest_animal from Parent class.
i.run_to_closest_animal(closest_sheep) # Works!
print(f"--> {wolf_count}-Wolf ({i}) after moving closer to "
f"CS ({closest_sheep }).")
print(f"___________ {wolf_count}-Wolf moved closer "
f"to CS___________")
# W.1.2.4
# If no CS found, wolf moves randomly based on move() method.
else:
print("-> No CS found!")
print(f"-->else1 begins: {wolf_count}-Wolf ({i}) "
f"before normal moving")
# Call move method from Parent class.
i.move()
print(f"-->else1 ends: {wolf_count}-Wolf ({i}) "
f"after normal moving")
print(f"....el1___________ {wolf_count}-Wolf moved "
f"normally___________") #el1 - keyword for debugging.
# W.1.3
#2. Else if the (current wolf store >min_energy AND p >= bf_e)
# (i.e., 0.5 if unchanged), it tries to find closest_wolf (CW) to move
# closer to, or if within action distance: breeds or fights. If no
# CW found, moves randomly.
elif i.store > min_energy and random.random() >= bf_e:
print(f"--> {wolf_count}-Wolf has > {min_energy} store ({i}) "
f"and Random >= {bf_e}. so will try to find CW to move "
f"closer or breed/fight if within AD({action_dist}).")
fight = False # Set fight as false. will turn True if fought.
breed = False # Will turn true if bred successfully.
fail_breed = False # Will turn true if failed breeding.
closest_wolf = None # To assign and track closest_wolf
wolf2_count = -1 # To assign and track other wolves.
# W.1.3.1
# Slice is used as wolves might breed new wolves.
# wolf_index is used as the starting index so that action is
# not duplicated for same wolf or same pair.
for j in wolves[wolf_index:]:
wolf2_count += 1 # Assign the other wolf it's index.
print(f"---> {wolf_count}-Wolf looping with "
f"{wolf2_count}-Wolf")
# Check distance between wolves
distance = i.dist_animals(j)
# W.1.3.1.A
# If a wolf between min_dist but beyond action_distance
# assign it as the closest wolf and update minimum distance
if action_dist < distance < min_dist:
print('----> (AD2 < D < MD).')
min_dist = distance # Assign (and update if closer found)
closest_wolf = j # Assign (and update if closer found)
print(f"----> Min distance={min_dist} for "
f"{wolf_count}-Wolf ({i}) with "
f"{wolf2_count}-Wolf ({j})")
# W.1.3.1.B
# If a wolf is within action distance: ACTION
elif distance <= action_dist:
print(f"----> d<=ad. breeding/fight proximity of "
f"{distance}(<={action_dist}) entered by "
f"{wolf_count}-Wolf ({i}) with CW "
f"{wolf2_count}-Wolf ({j}).")
# W.1.3.1.B.1
# If CW has > min store value (min_energy), breed
# successful if p is > bf_e (0.50 if unchanged). If
# p <= bf_e the wolves will try to breed but will fail.
if j.store > min_energy:
print(f"-----> CW {wolf2_count}-Wolf ({j} has > "
f"{min_energy} store. So they will try "
f"breeding. {bf_e*100}% probability of "
f"breeding successfully.")
# Breeding (successful/failed) costs energy and
# the new store value of both wolves will be the
# half of average of both wolves. This is same for
# sheep breeding.
# Call the breed_cost() method from Parent class.
breeding_cost = i.breed_cost(j)
# print('breeding_cost')
# W.1.3.1.B.1.1
if random.random() > bf_e:
print(f"------>1.A. Random >{bf_e}. "
f"So the wolves will mate.")
print(f"------> {wolf_count}-Wolf ({i}) before "
f"mating with CW {wolf2_count}-Wolf ({j})")
i.store = breeding_cost
j.store = breeding_cost
wolves.append(af.Wolf(environment, sheep,\
wolves, y=(i.y+5), x=(i.x+5)))
breed = True # Set breed as true!
print(f"------> Breed = {breed}")
print(f"------> {wolf_count}-Wolf ({i}) after "
f"mating with CW {wolf2_count}-Wolf ({j})")
print(f"_____________ {wolf_count}-Wolf Mated "
f"SUCCESSFULLY with "
f"CW {wolf2_count}-Wolf ____________")
# W.1.3.1.B.1.2
else: # p <= bf_e
print(f"----->1.B. Random <{bf_e}. "
f"So breeding will not be successful.")
print(f"----->{wolf_count}-Wolf ({i}) before "
f"fail breeding with "
f"CW {wolf2_count}-Wolf ({j})")
# Assign the new store values
i.store = breeding_cost
j.store = breeding_cost
fail_breed = True # Set fail_breed as True
print(f"------> Fail Breeding = {fail_breed}")
print(f"_____________ {wolf_count}-Wolf ({i}) "
f"after FAILED BREEDING with "
f"CW {wolf2_count}-Wolf ({j})")
# W.1.3.1.B.1.3
# Current wolf attempted breeding (successfully or
# unsuccessfully) with the first CW. So should not
# try with other CW even if within action distance.
# So break and go below to if and continue to next
# wolf.
break
# W.1.3.1.B.2
# If CW is within action distance but energy < minimum
# energy, then the wolves will fight! The winner depends
# on probability. If p > bf_e current wolf will win
# bf_e*store of the CW and vice versa if p <= bf_e.
# So if bf_e unchanged, p is 0.50. So if p>0.50, current
# wolf will 0.5*store of CW, which is half the store.
else:
print(f"----->2.A. CW {wolf2_count}-Wolf has "
f"< than {min_energy} store")
print(f"------>{wolf_count}-Wolf ({i}) will FIGHT "
f"CW {wolf2_count}-Wolf ({j}) & move")
# W.1.3.1.B.2.1
if random.random() > bf_e:
print(f"------>2.A.i. Random > {bf_e}. So "
f"{wolf_count}-Wolf will win {bf_e*100}% "
f"of CW {wolf2_count}-Wolf store.")
# This is what current wolf i will gain and CW j
# will loose. See above description.
gain = (j.store*bf_e)
i.store += gain
j.store -= gain
# The current wolf will also move after fighting
# based on move method.
i.move()
print(f"------> {wolf_count}-Wolf ({i}) won+moved"
f" after fighting with "
f"CW {wolf2_count}-Wolf ({j})")
# W.1.3.1.B.2.2
# if p<= bf_e
else:
print(f"------>2.A.ii. Random < {bf_e}. So "
f"{wolf_count}-Wolf will loose {bf_e*100}% "
f"store to CW {wolf2_count}-Wolf")
# This is what current wolf i will lose and CW j
# will gain. See above description.
lose = (i.store*bf_e)
i.store -= lose
j.store += lose
# The current wolf will also move after fighting
# based on move method.
i.move()
print(f"------>{wolf_count}-Wolf ({i}) lost+moved"
f" after fighting with CW "
f"{wolf2_count}-Wolf ({j})")
# W.1.3.1.B.2.3
# Set fight as true for current wolf!
fight = True
print(f"------> Fight = {fight}")
print(f"_____________ {wolf_count}-Wolf Fought-(2) "
f"with CW {wolf2_count}-Wolf ____________")
# Current wolf already fought with CW within
# action distance. So break and go to below if
# and continue to next wolf!
break
# W.1.3.2
# If current wolf interacted with CW within action distance,
# continue to the next wolf.
if breed or fail_breed or fight == True:
print(f"_______ Breed={breed}, Failed Breed={fail_breed}, "
f"Fought={fight} for {wolf_count}-Wolf __________")
continue
# W.1.3.3
# If CW found within min_distance but beyond action distance,
# get close to it.
elif closest_wolf != None:
print("->CW Found!")
print(f"-> {wolf_count}-Wolf ({i}) before moving closer to "
f"CW-Wolf: ({closest_wolf}).")
# Call run_to_closest_animal() method from Parent class.
i.run_to_closest_animal(closest_wolf) # Works!
print(f"-> {wolf_count}-Wolf ({i}) after moving closer to "
f"CW-Wolf: ({closest_wolf}).")
print(f"_______ {wolf_count}-Wolf moved closer CW+_______")
# W.1.3.4
# If No CW found, move as per the move method.
else: # No CW. E>min_energy and p>bf_e
print(f"-> No CW found. although store >{min_energy} and "
f"p>={bf_e}!")
print(f"--->else2 begins: {wolf_count}-Wolf ({i}) before "
f"normal moving.")
# Call move method from Parent class.
i.move()
print(f"--->else2 begins: {wolf_count}-Wolf ({i}) after "
f"normal moving.")
print(f".... el2 _______ {wolf_count}-Wolf "
f"moved normally _______ ")
# W.1.4
# If if and elif not satisfied, just move based on move method.
else:
# This area may also be made as as a rest area for wolves
# based on probability.
print("--> if and elif not satisfied")