Hier werden die Unterschiede zwischen zwei Versionen gezeigt.
Beide Seiten der vorigen Revision Vorhergehende Überarbeitung Nächste Überarbeitung | Vorhergehende Überarbeitung | ||
ws2425:lebewesenverhalten [2025/05/05 19:26] stefanborn [Unser Code:] |
ws2425:lebewesenverhalten [2025/05/05 19:41] (aktuell) stefanborn [Unser Code:] |
||
---|---|---|---|
Zeile 128: | Zeile 128: | ||
====Unser Code:==== | ====Unser Code:==== | ||
+ | |||
+ | <code python>import tkinter as tk | ||
+ | import random as r | ||
+ | import math as m | ||
+ | import numba | ||
+ | import matplotlib.pyplot as plt | ||
+ | from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg | ||
+ | |||
+ | fenster = tk.Tk() | ||
+ | fenster.title("Lebewesenverhalten") | ||
+ | |||
+ | """ | ||
+ | fenster.attributes("-fullscreen", True) | ||
+ | def beenden(event=None): | ||
+ | fenster.attributes("-fullscreen", False) | ||
+ | exit() | ||
+ | fenster.bind("<Escape>", beenden) | ||
+ | |||
+ | width = fenster.winfo_screenwidth() | ||
+ | hight = fenster.winfo_screenheight() | ||
+ | """ | ||
+ | |||
+ | fenster.geometry("1000x600") | ||
+ | |||
+ | width = 1000 | ||
+ | hight = 600 | ||
+ | |||
+ | |||
+ | canvas = tk.Canvas(fenster, bg="pink") | ||
+ | canvas.pack(fill="both",expand=True) | ||
+ | |||
+ | def end_simulation(event): | ||
+ | fenster.destroy() | ||
+ | fenste = tk.Tk() | ||
+ | fenste.title("Statistik") | ||
+ | fenste.geometry("1000x600") | ||
+ | fig, ax = plt.subplots(figsize=(6,4)) | ||
+ | ax.plot(x_werte, plant_values) | ||
+ | ax.plot(x_werte, meat_values) | ||
+ | canvas = FigureCanvasTkAgg(fig, master=fenste) | ||
+ | canvas.draw() | ||
+ | canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) | ||
+ | |||
+ | fenster.bind("<Return>", end_simulation) | ||
+ | |||
+ | start_pflanzenfresser = 50 | ||
+ | start_fleischfresser = 5 | ||
+ | anzahl_seen = 1 | ||
+ | lines = [] | ||
+ | dis = 100 | ||
+ | time = 0 | ||
+ | pflanzenfresser = [] | ||
+ | new_pflanzenfresser = [] | ||
+ | fleischfresser = [] | ||
+ | new_fleischfresser = [] | ||
+ | seen = [] | ||
+ | x_werte = [] | ||
+ | plant_values = [] | ||
+ | meat_values = [] | ||
+ | |||
+ | def update(event = None): | ||
+ | global time | ||
+ | if time % 50 == 0: | ||
+ | x_werte.append(time) | ||
+ | plant_values.append(len(pflanzenfresser)) | ||
+ | for tier in pflanzenfresser: | ||
+ | if not tier.alive: | ||
+ | plant_values[-1] -= 1 | ||
+ | meat_values.append(len(fleischfresser)) | ||
+ | for tier in fleischfresser: | ||
+ | if not tier.alive: | ||
+ | meat_values[-1] -= 1 | ||
+ | #for z in range(len(lines)): | ||
+ | # canvas.delete(lines[z]) | ||
+ | global new_pflanzenfresser | ||
+ | global new_fleischfresser | ||
+ | for baby in new_pflanzenfresser: | ||
+ | pflanzenfresser.append(baby) | ||
+ | baby.show() | ||
+ | new_pflanzenfresser = [] | ||
+ | for baby in new_fleischfresser: | ||
+ | fleischfresser.append(baby) | ||
+ | baby.show() | ||
+ | new_fleischfresser = [] | ||
+ | |||
+ | for pflanzenfresse in pflanzenfresser: | ||
+ | if pflanzenfresse.alive: | ||
+ | if time % 5 == 2: | ||
+ | pflanzenfresse.sight() | ||
+ | pflanzenfresse.check_grazing() | ||
+ | pflanzenfresse.change_food(-5*pflanzenfresse.speed) | ||
+ | if pflanzenfresse.mating_cooldown > 0: | ||
+ | pflanzenfresse.mating_cooldown -= 5 | ||
+ | pflanzenfresse.move() | ||
+ | |||
+ | for fleischfresse in fleischfresser: | ||
+ | if fleischfresse.alive: | ||
+ | if time % 5 == 0: | ||
+ | fleischfresse.sight() | ||
+ | fleischfresse.change_food(-5*fleischfresse.speed) | ||
+ | if fleischfresse.mating_cooldown > 0: | ||
+ | fleischfresse.mating_cooldown -= 5 | ||
+ | fleischfresse.move() | ||
+ | time += 1 | ||
+ | #fenster.bind("<space>", update) | ||
+ | canvas.after(20, update) | ||
+ | |||
+ | def det(alpha, beta): | ||
+ | return m.sin(m.radians(beta-alpha)) | ||
+ | |||
+ | #Distanz zwischen zwei Punkten | ||
+ | def distance(x1, x2, y1, y2): | ||
+ | return m.sqrt((x1 - x2)**2 + (y1 - y2)**2) | ||
+ | |||
+ | #Quadrat des Abstandes zweier Punkte (für schnelle Berechnungen) | ||
+ | def squared_distance(x1, x2, y1, y2): | ||
+ | return (x1 - x2)**2 + (y1 - y2)**2 | ||
+ | |||
+ | class Objekt: | ||
+ | def __init__(self, x, y): | ||
+ | self.x = x | ||
+ | self.y = y | ||
+ | |||
+ | class Tiere(Objekt): | ||
+ | def __init__(self, x, y , direction, health = 1000, food = 10000, a = 40, b = 180, color = "black", status = "wandering"): | ||
+ | #Eigenschaften | ||
+ | super().__init__(x, y) | ||
+ | self.direction = direction | ||
+ | self.direction_offset = 0 | ||
+ | self.health = health | ||
+ | self.food = food | ||
+ | self.color = color | ||
+ | self.mating_cooldown = 0 | ||
+ | #Sicht | ||
+ | self.sichtweite = a | ||
+ | self.sichtkegelwinkel = b | ||
+ | #Eigenschaften, von denen das Handeln abhängt | ||
+ | self.alive = True | ||
+ | self.status = "wandering" | ||
+ | self.threat = None | ||
+ | self.target = None | ||
+ | self.avoiding = None | ||
+ | |||
+ | def change_food(self, change): | ||
+ | if self.food > 0 or change > 0: | ||
+ | self.food += change | ||
+ | elif self.food <= 0: | ||
+ | self.change_health(-10) | ||
+ | |||
+ | #tiere verlieren teile ihrer Gesundheit | ||
+ | def change_health(self, change): | ||
+ | self.health += change | ||
+ | if self.health <= 0: | ||
+ | self.die() | ||
+ | |||
+ | #Änderung der Richtung | ||
+ | def change_direction(self, type, change): | ||
+ | if type == "set": | ||
+ | self.direction = change | ||
+ | elif type == "add": | ||
+ | self.direction += change | ||
+ | self.direction %= 360 | ||
+ | |||
+ | #tiere werden wenn sie keine Lebenspunkte mehr haben aus der update-schleife genommen | ||
+ | def die(self): | ||
+ | self.alive = False | ||
+ | if isinstance(self, Fleischfresser): | ||
+ | print(self.id) | ||
+ | self.hide() | ||
+ | |||
+ | #bewegt ein Tier vorwärts abhängig von "speed" und "direction" | ||
+ | def forward(self): | ||
+ | self.x += self.speed*m.cos(m.radians(self.direction+self.direction_offset)) | ||
+ | self.y += self.speed*m.sin(m.radians(self.direction+self.direction_offset)) | ||
+ | self.hide() | ||
+ | self.show() | ||
+ | |||
+ | #beschreibt das Verhalten eines Tieres während seines sogenannten Zuges basierend auf dem Status | ||
+ | def move(self): | ||
+ | if self.status == "wandering": | ||
+ | self.change_direction("add", r.randint(-5,5)) | ||
+ | self.forward() | ||
+ | |||
+ | elif self.status == "fleeing": | ||
+ | if squared_distance(self.x, self.threat.x, self.y, self.threat.y) >= 90000 and self.threat.target != self: | ||
+ | self.status = "wandering" | ||
+ | self.threat = None | ||
+ | self.speed = self.walking_speed | ||
+ | self.forward() | ||
+ | |||
+ | elif self.status == "hunting": | ||
+ | self.change_direction("set", m.degrees(m.atan2((self.target.y-self.y),(self.target.x-self.x)))) | ||
+ | if (self.x-self.target.x)**2+(self.y-self.target.y)**2 <= (self.radius+self.target.radius)**2: | ||
+ | self.collision() | ||
+ | self.forward() | ||
+ | |||
+ | #Visualisierung der Tiere | ||
+ | def show(self): | ||
+ | self.id = canvas.create_oval(self.x-self.radius, self.y-self.radius, self.x+self.radius, self.y+self.radius, fill=self.color) | ||
+ | |||
+ | def hide(self): | ||
+ | canvas.delete(self.id) | ||
+ | |||
+ | |||
+ | #def det(alpha, beta): | ||
+ | # return m.sin(m.radians(beta-alpha)) | ||
+ | #Überprüft ob die Richtung des Hindernisses im Wahrnehmungsbereich des Tieres liegt | ||
+ | def in_sight(self, avoid): | ||
+ | direction_left_edge = (self.direction + self.direction_offset) + (self.sichtkegelwinkel / 2) #in der Visualiesierung sind die Seiten vertauscht, weil y-Achse auch gespiegelt ist | ||
+ | direction_right_edge = (self.direction + self.direction_offset) - (self.sichtkegelwinkel / 2) #in der Visualiesierung sind die Seiten vertauscht, weil y-Achse auch gespiegelt ist | ||
+ | return det(direction_left_edge, avoid) < 0 and det(avoid, direction_right_edge) < 0 | ||
+ | |||
+ | #Wahrnehmung der Umwelt mithilfe von Stichpunktartigen Zustandschecks entlang der sightrichtung ("direction") | ||
+ | def sight(self): | ||
+ | self.avoiding = None | ||
+ | |||
+ | potential_targets = [] | ||
+ | potential_threats = [] | ||
+ | |||
+ | #debug: | ||
+ | #lines.append(canvas.create_line(self.x, self.y, self.x+dis*m.cos(m.radians((self.direction + self.direction_offset) + (self.sichtkegelwinkel / 2))), self.y+dis*m.sin(m.radians((self.direction + self.direction_offset) + (self.sichtkegelwinkel / 2))), fill = "orange")) | ||
+ | #lines.append(canvas.create_line(self.x, self.y, self.x+dis*m.cos(m.radians(self.direction+self.direction_offset)), self.y+dis*m.sin(m.radians(self.direction+self.direction_offset)), fill = "orange")) | ||
+ | #lines.append(canvas.create_line(self.x, self.y, self.x+dis*m.cos(m.radians((self.direction + self.direction_offset) - (self.sichtkegelwinkel / 2))), self.y+dis*m.sin(m.radians((self.direction + self.direction_offset) - (self.sichtkegelwinkel / 2))), fill = "orange")) | ||
+ | |||
+ | |||
+ | #Kollision mit der Ecke links oben verhindern | ||
+ | if m.sin(m.radians(self.direction+self.direction_offset)) <= 0 and m.cos(m.radians(self.direction+self.direction_offset)) <= 0: | ||
+ | if self.x > dis and self.y <= dis: | ||
+ | self.avoiding = 270 | ||
+ | elif self.x <= dis and self.y > dis: | ||
+ | self.avoiding = 180 | ||
+ | elif self.x <= dis and self.y <= dis: | ||
+ | if self.avoiding == None: | ||
+ | self.avoiding = 180 | ||
+ | |||
+ | #Kollision mit der Ecke rechts oben verhindern | ||
+ | elif m.sin(m.radians(self.direction+self.direction_offset)) <= 0 and m.cos(m.radians(self.direction+self.direction_offset)) > 0: | ||
+ | if self.x <= width-dis and self.y <= dis: | ||
+ | self.avoiding = 270 | ||
+ | elif self.x > width-dis and self.y > dis: | ||
+ | self.avoiding = 0 | ||
+ | elif self.x > width-dis and self.y <= dis: | ||
+ | if self.avoiding == None: | ||
+ | self.avoiding = 0 | ||
+ | |||
+ | #Kollision mit der Ecke links unten verhindern | ||
+ | elif m.sin(m.radians(self.direction+self.direction_offset)) > 0 and m.cos(m.radians(self.direction+self.direction_offset)) <= 0: | ||
+ | if self.x > dis and self.y > hight-dis: | ||
+ | self.avoiding = 90 | ||
+ | elif self.x <= dis and self.y <= hight-dis: | ||
+ | self.avoiding = 180 | ||
+ | elif self.x <= dis and self.y > hight-dis: | ||
+ | if self.avoiding == None: | ||
+ | self.avoiding = 180 | ||
+ | |||
+ | #Kollision mit der Ecke rechts unten verhindern | ||
+ | elif m.sin(m.radians(self.direction+self.direction_offset)) > 0 and m.cos(m.radians(self.direction+self.direction_offset)) > 0: | ||
+ | if self.x <= width-dis and self.y > hight-dis: | ||
+ | self.avoiding = 90 | ||
+ | elif self.x > width-dis and self.y <= hight-dis: | ||
+ | self.avoiding = 0 | ||
+ | elif self.x > width-dis and self.y > hight-dis: | ||
+ | if self.avoiding == None: | ||
+ | self.avoiding = 0 | ||
+ | if self.avoiding != None: | ||
+ | self.avoid(self.avoiding) | ||
+ | |||
+ | |||
+ | for k in range(len(pflanzenfresser)): | ||
+ | if pflanzenfresser[k].alive and not ((isinstance(self, Pflanzenfresser) and k == self.number)): | ||
+ | direction_animal = m.degrees(m.atan2((pflanzenfresser[k].y-self.y),(pflanzenfresser[k].x-self.x))) | ||
+ | if self.in_sight(direction_animal): | ||
+ | self.react_to_pflanzenfresser(k, potential_targets) | ||
+ | |||
+ | for k in range(len(fleischfresser)): | ||
+ | if fleischfresser[k].alive and not (isinstance(self, Fleischfresser) and k == self.number): | ||
+ | direction_animal = m.degrees(m.atan2((fleischfresser[k].y-self.y),(fleischfresser[k].x-self.x))) | ||
+ | if self.in_sight(direction_animal): | ||
+ | self.react_to_fleischfresser(k, potential_threats) | ||
+ | |||
+ | if len(potential_targets) > 0: | ||
+ | self.set_target(self.find_animal(potential_targets)) | ||
+ | if len(potential_threats) > 0: | ||
+ | self.set_threat(self.find_animal(potential_threats)) | ||
+ | |||
+ | if self.avoiding == None: | ||
+ | self.change_direction("add", self.direction_offset) | ||
+ | self.direction_offset = 0 | ||
+ | |||
+ | #Ändert die Richtung, mithilfe einer Offset variable | ||
+ | def avoid(self, direction_avoid): | ||
+ | determinante = det(self.direction+self.direction_offset, direction_avoid) | ||
+ | if determinante < 0: | ||
+ | self.direction_offset += 10 | ||
+ | else: | ||
+ | self.direction_offset -= 10 | ||
+ | |||
+ | def find_animal(self, animals): | ||
+ | if len(animals) > 0: | ||
+ | nearest_animal = animals[0] | ||
+ | for i in range(1, len(animals)): | ||
+ | if squared_distance(self.x, animals[i].x, self.y, animals[i].y) < squared_distance(self.x, nearest_animal.x, self.y, nearest_animal.y): | ||
+ | nearstest_animal = animals[i] | ||
+ | return nearest_animal | ||
+ | |||
+ | #setzt den status auf "fleeing" | ||
+ | def set_threat(self, threat): | ||
+ | self.status = "fleeing" | ||
+ | self.threat = threat | ||
+ | self.speed = self.running_speed | ||
+ | self.change_direction("set", m.degrees(m.atan2((self.threat.y-self.y),(self.threat.x-self.x))) + 180) | ||
+ | |||
+ | #setzt den status auf "hunting" | ||
+ | def set_target(self, target): | ||
+ | self.status = "hunting" | ||
+ | self.target = target | ||
+ | self.color = "red" | ||
+ | self.speed = self.running_speed | ||
+ | |||
+ | #Wenn ein Fleischfresser einen Pflanzenfresser gefangen hat verliert der Pflanzenfresser leben bis er stirbt. Dann wird der Status des Fleischfressers wieder zurückgesetzt | ||
+ | def collision(self): | ||
+ | self.target.change_health(-100) | ||
+ | if self.target.alive == False: | ||
+ | self.change_food(500) | ||
+ | self.status = "wandering" | ||
+ | self.color = "blue" | ||
+ | self.speed = self.walking_speed | ||
+ | |||
+ | class Pflanzenfresser(Tiere): | ||
+ | radius = 5 | ||
+ | def __init__(self, i, x, y, direction): | ||
+ | self.number = i | ||
+ | self.grazing_speed = 0.1 | ||
+ | self.walking_speed = 0.5 | ||
+ | self.running_speed = 2.5 | ||
+ | self.speed = self.walking_speed | ||
+ | super().__init__(x, y, direction, 1000, 1000, 40, 180, "green") | ||
+ | |||
+ | def check_grazing(self): | ||
+ | if self.food < 900: | ||
+ | self.speed = self.grazing_speed | ||
+ | elif self.food >= 1000: | ||
+ | self.speed = self.walking_speed | ||
+ | if self.speed == self.grazing_speed: | ||
+ | self.change_food(1) | ||
+ | |||
+ | def react_to_pflanzenfresser(self, k, potential_threats): | ||
+ | if self.status == "wandering" and pflanzenfresser[k].status == "wandering": | ||
+ | if squared_distance(self.x, pflanzenfresser[k].x, self.y, pflanzenfresser[k].y) < 4*self.radius**2: | ||
+ | if self.mating_cooldown == 0 and pflanzenfresser[k].mating_cooldown == 0: | ||
+ | if self.food >= 500 and pflanzenfresser[k].food >= 500: | ||
+ | if len(pflanzenfresser) <= 400: | ||
+ | global new_pflanzenfresser | ||
+ | random_direction = r.randint(0,359) | ||
+ | new_pflanzenfresser.append(Pflanzenfresser(len(pflanzenfresser)+len(new_pflanzenfresser), self.x, self.y, random_direction)) | ||
+ | self.mating_cooldown = 1000 | ||
+ | pflanzenfresser[k].mating_cooldown = 1000 | ||
+ | elif squared_distance(self.x, pflanzenfresser[k].x, self.y, pflanzenfresser[k].y) < 10000: | ||
+ | if self.mating_cooldown == 0: | ||
+ | self.change_direction("set", m.degrees(m.atan2((pflanzenfresser[k].y-self.y),(pflanzenfresser[k].x-self.x)))) | ||
+ | |||
+ | def react_to_fleischfresser(self, k, potential_threats): | ||
+ | quadratischeDistanz = squared_distance(self.x, fleischfresser[k].x, self.y, fleischfresser[k].y) | ||
+ | if quadratischeDistanz <= dis**2: | ||
+ | potential_threats.append(fleischfresser[k]) | ||
+ | |||
+ | class Fleischfresser(Tiere): | ||
+ | radius = 8 | ||
+ | def __init__(self, i, x, y, direction): | ||
+ | self.number = i | ||
+ | self.walking_speed = 0.8 | ||
+ | self.running_speed = 2 | ||
+ | self.speed = self.walking_speed | ||
+ | super().__init__(x, y, direction, 1000, 1000, 100, 120, "blue") | ||
+ | |||
+ | def react_to_pflanzenfresser(self, k, potential_targets): | ||
+ | quadratischeDistanz = squared_distance(self.x, pflanzenfresser[k].x, self.y, pflanzenfresser[k].y) | ||
+ | if quadratischeDistanz <= 200**2: | ||
+ | if self.food < 500: | ||
+ | if self.status == "wandering": | ||
+ | potential_targets.append(pflanzenfresser[k]) | ||
+ | elif self.status == "hunting": | ||
+ | if quadratischeDistanz < squared_distance(self.x, self.target.x, self.y, self.target.y): | ||
+ | potential_targets.append(pflanzenfresser[k]) | ||
+ | |||
+ | def react_to_fleischfresser(self, k, potential_targets): | ||
+ | if self.status == "wandering" and fleischfresser[k].status == "wandering": | ||
+ | if squared_distance(self.x, pflanzenfresser[k].x, self.y, pflanzenfresser[k].y) < 4*self.radius**2: | ||
+ | if self.mating_cooldown == 0 and fleischfresser[k].mating_cooldown == 0: | ||
+ | if self.food >= 500 and fleischfresser[k].food >= 500: | ||
+ | global new_fleischfresser | ||
+ | random_direction = r.randint(0,359) | ||
+ | new_fleischfresser.append(Fleischfresser(len(fleischfresser)+len(new_fleischfresser), self.x, self.y, random_direction)) | ||
+ | self.mating_cooldown = 1000 | ||
+ | fleischfresser[k].mating_cooldown = 1000 | ||
+ | elif squared_distance(self.x, fleischfresser[k].x, self.y, fleischfresser[k].y) < 40000: | ||
+ | if self.mating_cooldown == 0: | ||
+ | self.change_direction("set", m.degrees(m.atan2((fleischfresser[k].y-self.y),(fleischfresser[k].x-self.x)))) | ||
+ | |||
+ | class See(Objekt): | ||
+ | radius = 50 | ||
+ | def __init__(self, number, x = 50, y = 50): | ||
+ | self.number = number | ||
+ | self.radius = radius | ||
+ | self.x = x | ||
+ | self.y = y | ||
+ | self.color = "blue" | ||
+ | super().__init__(x, y) | ||
+ | |||
+ | #Simulationsaufbau | ||
+ | for i in range(start_pflanzenfresser): | ||
+ | random_x = r.randint(Pflanzenfresser.radius, width - Pflanzenfresser.radius) | ||
+ | random_y = r.randint(Pflanzenfresser.radius, hight - Pflanzenfresser.radius) | ||
+ | random_direction = r.randint(0,359) | ||
+ | pflanzenfresser.append(Pflanzenfresser(i, random_x, random_y , random_direction)) | ||
+ | pflanzenfresser[i].show() | ||
+ | |||
+ | for i in range(start_fleischfresser): | ||
+ | random_x = r.randint(Fleischfresser.radius, width - Fleischfresser.radius) | ||
+ | random_y = r.randint(Fleischfresser.radius, hight - Fleischfresser.radius) | ||
+ | random_direction = r.randint(0,359) | ||
+ | fleischfresser.append(Fleischfresser(i, random_x, random_y, random_direction)) | ||
+ | fleischfresser[i].show() | ||
+ | |||
+ | |||
+ | for i in range(anzahl_seen): ###hiermit habe ich den See und Strand gezeichnet | ||
+ | | ||
+ | id = canvas.create_rectangle(0, 70, 100, 0, fill= "yellow", outline = "yellow") # Das ist die Füllung vom Loch oben links | ||
+ | | ||
+ | id = canvas.create_rectangle(0, 200, 100, 0, fill= "yellow", outline = "yellow") | ||
+ | | ||
+ | id = canvas.create_oval(0, 250, 100, 0, fill= "yellow", outline = "yellow") | ||
+ | |||
+ | id = canvas.create_oval(0, 245, 50, 150, fill= "yellow", outline = "yellow") | ||
+ | |||
+ | id = canvas.create_oval(0, 255, 70, 150, fill= "yellow", outline = "yellow") | ||
+ | |||
+ | id = canvas.create_oval(-50, 255, 30, 100, fill= "yellow", outline = "yellow") | ||
+ | |||
+ | id = canvas.create_oval(-100, 260, 100, 100, fill= "yellow", outline = "yellow") | ||
+ | |||
+ | id = canvas.create_oval(-100, 260, 200, 100, fill= "yellow", outline = "yellow") | ||
+ | |||
+ | |||
+ | |||
+ | | ||
+ | | ||
+ | | ||
+ | id = canvas.create_oval(0, 150, 100, 0, fill= "blue", outline = "blue") | ||
+ | |||
+ | id = canvas.create_oval(300, 150, 170, 0, fill= "blue", outline = "blue") | ||
+ | |||
+ | id = canvas.create_oval(50, 100, 240, 200, fill= "blue", outline = "blue") | ||
+ | |||
+ | id = canvas.create_rectangle(50, 0, 220, 150, fill= "blue", outline = "blue") | ||
+ | |||
+ | id = canvas.create_oval(250, 140, 220, 157, fill= "blue", outline = "blue") | ||
+ | |||
+ | id = canvas.create_oval(16, 50, 220, 190, fill= "blue", outline = "blue") | ||
+ | update() | ||
+ | </code> | ||
- | ```python | ||
- | a = 1 | ||
- | ``` | ||