Kivy, Python рдореЗрдВ Android рдХреЗ рд▓рд┐рдП рдПрдХ рд╕рд╛рдБрдк рд▓рд┐рдЦрдирд╛

рдирдорд╕реНрддреЗ!

рдмрд╣реБрдд рд╕реЗ рд▓реЛрдЧ рдПрдВрдбреНрд░реЙрдЗрдб рдкрд░ рдкреНрд░реЛрдЧреНрд░рд╛рдорд┐рдВрдЧ рд╢реБрд░реВ рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рдПрдВрдбреНрд░реЙрдЗрдб рд╕реНрдЯреВрдбрд┐рдпреЛ рдФрд░ рдЬрд╛рд╡рд╛ рдЙрдиреНрд╣реЗрдВ рдбрд░рд╛рддреЗ рд╣реИрдВред рдХреНрдпреЛрдВ? рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рдПрдХ рдЕрд░реНрде рдореЗрдВ рдЧреМрд░реИрдпрд╛ рдкрд░ рддреЛрдк рд╕реЗ рд╣реИред "рдореИрдВ рд╕рд┐рд░реНрдл рдПрдХ рд╕рд╛рдВрдк рдмрдирд╛рдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВ, рдмрд╕ рдЗрддрдирд╛ рд╣реА!"



рдЪрд▓реЛ рд╢реБрд░реВ рд╣реЛ рдЬрд╛рдУ! (рдЕрдВрдд рдореЗрдВ рдмреЛрдирд╕)

рдХрд┐рд╡реА рдкрд░ рдПрдХ рдФрд░ рд╕рд╛рдБрдк рдЯреНрдпреВрдЯреЛрд░рд┐рдпрд▓ рдХреНрдпреЛрдВ рдмрдирд╛рдПрдВ? (рдкрдврд╝рдиреЗ рдХреЗ рд▓рд┐рдП рд╡реИрдХрд▓реНрдкрд┐рдХ)
рдпрджрд┐ рдЖрдк рдПрдХ рдЕрдЬрдЧрд░ рд╣реИрдВ рдФрд░ рдПрдВрдбреНрд░реЙрдЗрдб рдХреЗ рд▓рд┐рдП рд╕рд░рд▓ рдЧреЗрдо рд╡рд┐рдХрд╕рд┐рдд рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ, рддреЛ рдЖрдкрдХреЛ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА "рдПрдВрдбреНрд░реЙрдЗрдб рдкрд░ рд╕рд╛рдБрдк" рдФрд░ рдЗрд╕реЗ (Eng) рдпрд╛ рдЗрд╕рдХреЗ рдЕрдиреБрд╡рд╛рдж (рд░рд╕) рдХреЛ рдвреВрдВрдврдирд╛ рдЪрд╛рд╣рд┐рдП ред рдФрд░ рдореИрдВрдиреЗ рдРрд╕рд╛ рдХрд┐рдпрд╛ рднреАред рджреБрд░реНрднрд╛рдЧреНрдп рд╕реЗ, рдореИрдВрдиреЗ рд▓реЗрдЦ рдХреЛ рдХрдИ рдХрд╛рд░рдгреЛрдВ рд╕реЗ рдмреЗрдХрд╛рд░ рдкрд╛рдпрд╛:

рдмреБрд░рд╛ рдХреЛрдб

рдорд╛рдореВрд▓реА рдЦрд╛рдорд┐рдпрд╛рдВ:

  1. "рдкреВрдВрдЫ" рдФрд░ "рд╕рд┐рд░" рдХрд╛ рдЙрдкрдпреЛрдЧ рдЕрд▓рдЧ-рдЕрд▓рдЧред рдпрд╣ рдЖрд╡рд╢реНрдпрдХ рдирд╣реАрдВ рд╣реИ, рдХреНрдпреЛрдВрдХрд┐ рд╕рд╛рдВрдк рдореЗрдВ рд╕рд┐рд░ рдкреВрдВрдЫ рдХрд╛ рдкрд╣рд▓рд╛ рд╣рд┐рд╕реНрд╕рд╛ рд╣реИред рдЖрдкрдХреЛ рдкреВрд░реЗ рд╕рд╛рдБрдк рдХреЛ рджреЛ рднрд╛рдЧреЛрдВ рдореЗрдВ рд╡рд┐рднрд╛рдЬрд┐рдд рдирд╣реАрдВ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП, рдЬрд┐рд╕рдХреЗ рд▓рд┐рдП рдХреЛрдб рдЕрд▓рдЧ рд╕реЗ рд▓рд┐рдЦрд╛ рдЧрдпрд╛ рд╣реИред
  2. Self.update рд╕реЗ Clock.schedule рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИ ... self.updateред
  3. рджреНрд╡рд┐рддреАрдп-рд╕реНрддрд░реАрдп рд╡рд░реНрдЧ (рд╕рд╢рд░реНрдд рд░реВрдк рд╕реЗ рдкреНрд░рдердо рд╢реНрд░реЗрдгреА рд╕реЗ рдкреНрд░рд╡реЗрд╢ рдмрд┐рдВрджреБ) рдкреНрд▓реЗрдЧреНрд░рд╛рдЙрдВрдб рд╢реБрд░реБрдЖрдд рдореЗрдВ рдШреЛрд╖рд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдлрд╝рд╛рдЗрд▓ рдХреЗ рдЕрдВрдд рдореЗрдВ рдкреНрд░рдердо-рд╕реНрддрд░реАрдп рд╡рд░реНрдЧ рд╕реНрдиреЗрдкрдРрдк рдШреЛрд╖рд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред
  4. рд╡реИрдХреНрдЯрд░ ((0, 1), (1, 0, ...) рдХреЗ рдмрдЬрд╛рдп рджрд┐рд╢рд╛рдУрдВ рдХреЗ рд▓рд┐рдП рдирд╛рдо ("рдКрдкрд░", "рдиреАрдЪреЗ", ...)ред


рдЧрдВрднреАрд░ рдиреБрдХрд╕рд╛рди:
  1. рдЧрддрд┐рд╢реАрд▓ рдСрдмреНрдЬреЗрдХреНрдЯ (рдЬреИрд╕реЗ рдлрд▓) рдХреЗрд╡реА рдлрд╝рд╛рдЗрд▓ рд╕реЗ рдЬреБрдбрд╝реЗ рд╣реЛрддреЗ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рдЖрдк рдЖрдзреЗ рдХреЛрдб рдХреЛ рдлрд┐рд░ рд╕реЗ рд▓рд┐рдЦреЗ рдмрд┐рдирд╛ рдПрдХ рд╕реЗ рдЕрдзрд┐рдХ рд╕реЗрдм рдирд╣реАрдВ рдмрдирд╛ рд╕рдХрддреЗ рд╣реИрдВ
  2. рд╕реЗрд▓-рдмрд╛рдп-рд╕реЗрд▓ рдХреЗ рдмрдЬрд╛рдп рд╕рд╛рдВрдк рдХреЛ рд╣рд┐рд▓рд╛рдиреЗ рдХрд╛ рдЕрджреНрднреБрдд рддрд░реНрдХред
  3. 350 рд▓рд╛рдЗрдиреЗрдВ - рдХреЛрдб рдмрд╣реБрдд рд▓рдВрдмрд╛ рд╣реИред

рд▓реЗрдЦ рд╢реБрд░реБрдЖрддреА рдХреЗ рд▓рд┐рдП рд╕реНрдкрд╖реНрдЯ рдирд╣реАрдВ рд╣реИред

рдпрд╣ рдореЗрд░реА рдирд┐рдЬреА рд░рд╛рдп рд╣реИред рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдореИрдВ рдЧрд╛рд░рдВрдЯреА рдирд╣реАрдВ рджреЗ рд╕рдХрддрд╛ рдХрд┐ рдореЗрд░рд╛ рд▓реЗрдЦ рдЕрдзрд┐рдХ рд░реЛрдЪрдХ рдФрд░ рд╕рдордЭрдиреЗ рдпреЛрдЧреНрдп рд╣реЛрдЧрд╛ред рд▓реЗрдХрд┐рди рдореИрдВ рдХреЛрд╢рд┐рд╢ рдХрд░реВрдВрдЧрд╛, рдФрд░ рдореИрдВ рдЧрд╛рд░рдВрдЯреА рджреЗрддрд╛ рд╣реВрдВ:

  1. рдХреЛрдб рдХрдо рд╣реЛрдЧрд╛
  2. рд╕реБрдВрджрд░ рд╕рд╛рдВрдк (рдЕрдкреЗрдХреНрд╖рд╛рдХреГрдд)
  3. рдЯреНрдпреВрдЯреЛрд░рд┐рдпрд▓ рдХрд╛ рдЪрд░рдгрдмрджреНрдз рд╡рд┐рдХрд╛рд╕ рд╣реЛрдЧрд╛ред

рдЗрд╕рдХрд╛ рдкрд░рд┐рдгрд╛рдо рдХрдорд▓ рдЗрд▓ рдлреВрдЯ рдирд╣реАрдВ рд╣реИ


рдХреЛрд╢рд┐рдХрд╛рдУрдВ, рдПрдХ рдЕрджреНрднреБрдд рддреНрд░рд┐рдХреЛрдг, рдПрдХ рдЭрдЯрдХреЗрджрд╛рд░ рд╕рд╛рдБрдк рдХреЗ рдмреАрдЪ рдХреЛрдИ рджреВрд░реА рдирд╣реАрдВ рд╣реИред

рдкрд░рд┐рдЪрд┐рдд


рдкрд╣рд▓рд╛ рдЖрд╡реЗрджрди


рдХреГрдкрдпрд╛ рдХрд┐рд╡реА рдХреЛ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдирд╛ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░реЗрдВ (рдпрджрд┐ рдирд╣реАрдВ, рдирд┐рд░реНрджреЗрд╢реЛрдВ рдХрд╛ рдкрд╛рд▓рди рдХрд░реЗрдВ ) рдФрд░ рдЪрд▓рд╛рдПрдВ
buildozer init рдкрд░рд┐рдпреЛрдЬрдирд╛ рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ buildozer init ред

рдкрд╣рд▓рд╛ рдХрд╛рд░реНрдпрдХреНрд░рдо рдЪрд▓рд╛рдПрдВ:

main.py

 from kivy.app import App from kivy.uix.widget import Widget class WormApp(App): def build(self): return Widget() if __name__ == '__main__': WormApp().run() 



рд╣рдордиреЗ рдПрдХ рд╡рд┐рдЬреЗрдЯ рдмрдирд╛рдпрд╛ рд╣реИред рдЗрд╕реА рддрд░рд╣, рд╣рдо рдПрдХ рдмрдЯрди рдпрд╛ рдЧреНрд░рд╛рдлрд┐рдХрд▓ рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдХреЗ рдХрд┐рд╕реА рдЕрдиреНрдп рддрддреНрд╡ рдХреЛ рдмрдирд╛ рд╕рдХрддреЗ рд╣реИрдВ:

 from kivy.app import App from kivy.uix.widget import Widget from kivy.uix.button import Button class WormApp(App): def build(self): self.but = Button() self.but.pos = (100, 100) self.but.size = (200, 200) self.but.text = "Hello, cruel world" self.form = Widget() self.form.add_widget(self.but) return self.form if __name__ == '__main__': WormApp().run() 



рд╣реБрд░реНрд░реЗ! рдмрдзрд╛рдИ! рдЖрдкрдиреЗ рдПрдХ рдмрдЯрди рдмрдирд╛рдпрд╛ рд╣реИ!

.Kv рдлрд╝рд╛рдЗрд▓реЗрдВ


рд╣рд╛рд▓рд╛рдВрдХрд┐, рдРрд╕реЗ рддрддреНрд╡реЛрдВ рдХреЛ рдмрдирд╛рдиреЗ рдХрд╛ рдПрдХ рдФрд░ рддрд░реАрдХрд╛ рд╣реИред рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рд╣рдорд╛рд░реЗ рдлреЙрд░реНрдо рдХреА рдШреЛрд╖рдгрд╛ рдХрд░реЗрдВ:

 from kivy.app import App from kivy.uix.widget import Widget from kivy.uix.button import Button class Form(Widget): def __init__(self): super().__init__() self.but1 = Button() self.but1.pos = (100, 100) self.add_widget(self.but1) class WormApp(App): def build(self): self.form = Form() return self.form if __name__ == '__main__': WormApp().run() 

рдлрд┐рд░ "worm.kv" рдлрд╝рд╛рдЗрд▓ рдмрдирд╛рдПрдБред

worm.kv

 <Form>: but2: but_id Button: id: but_id pos: (200, 200) 

рдХреНрдпрд╛ рд╣реБрдЖ? рд╣рдордиреЗ рдПрдХ рдФрд░ рдмрдЯрди рдмрдирд╛рдпрд╛ рдФрд░ id but_id рдЕрд╕рд╛рдЗрди рдХрд┐рдпрд╛ред рдЕрдм but_id but2 рд░реВрдкреЛрдВ рд╕реЗ рд╕рдВрдмрджреНрдз рд╣реИред рдЗрд╕рдХрд╛ рдорддрд▓рдм рдпрд╣ рд╣реИ рдХрд┐ рд╣рдо рдмрдЯрди рдХреЛ but2 рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:

 class Form(Widget): def __init__(self): super().__init__() self.but1 = Button() self.but1.pos = (100, 100) self.add_widget(self.but1) # self.but2.text = "OH MY" 



рдЧреНрд░рд╛рдлрд┐рдХреНрд╕


рдЕрдЧрд▓рд╛, рдПрдХ рдЧреНрд░рд╛рдлрд┐рдХ рддрддреНрд╡ рдмрдирд╛рдПрдВред рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рдЗрд╕реЗ worm.kv рдореЗрдВ рдШреЛрд╖рд┐рдд рдХрд░реЗрдВ:

 <Form>: <Cell>: canvas: Rectangle: size: self.size pos: self.pos 

рд╣рдордиреЗ рдЖрдпрдд рдХреА рд╕реНрдерд┐рддрд┐ рдХреЛ self.size рдФрд░ рд╕реНрд╡рдпрдВ рдХреЗ рдЖрдХрд╛рд░ рдХреЗ рд╕рд╛рде рдЬреЛрдбрд╝рд╛ред рдЗрд╕рд▓рд┐рдП рдЕрдм рдпреЗ рдЧреБрдг рд╕реЗрд▓ рд╕реЗ рдЙрдкрд▓рдмреНрдз рд╣реИрдВ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЬреИрд╕реЗ рд╣реА рд╣рдо рд╕реЗрд▓ рдмрдирд╛рддреЗ рд╣реИрдВ, рд╣рдо рдЗрд╕рдХрд╛ рдЖрдХрд╛рд░ рдФрд░ рд╕реНрдерд┐рддрд┐ рдмрджрд▓ рд╕рдХрддреЗ рд╣реИрдВ:

 class Cell(Widget): def __init__(self, x, y, size): super().__init__() self.size = (size, size) #   ,    self.size    "size"  self.pos = (x, y) class Form(Widget): def __init__(self): super().__init__() self.cell = Cell(100, 100, 30) self.add_widget(self.cell) 



рдареАрдХ рд╣реИ, рд╣рдордиреЗ рдПрдХ рдкрд┐рдВрдЬрд░рд╛ рдмрдирд╛рдпрд╛ред

рдЖрд╡рд╢реНрдпрдХ рддрд░реАрдХреЗ


рд╕рд╛рдВрдк рдХреЛ рд╣рд┐рд▓рд╛рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░рддреЗ рд╣реИрдВред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо Form.update рдлрд╝рдВрдХреНрд╢рди рдХреЛ рдЬреЛрдбрд╝ рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ Clock.schedule рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЕрдиреБрд╕реВрдЪреА рдореЗрдВ рдмрд╛рдБрдз рд╕рдХрддреЗ рд╣реИрдВред

 from kivy.app import App from kivy.uix.widget import Widget from kivy.clock import Clock class Cell(Widget): def __init__(self, x, y, size): super().__init__() self.size = (size, size) self.pos = (x, y) class Form(Widget): def __init__(self): super().__init__() self.cell = Cell(100, 100, 30) self.add_widget(self.cell) def start(self): Clock.schedule_interval(self.update, 0.01) def update(self, _): self.cell.pos = (self.cell.pos[0] + 2, self.cell.pos[1] + 3) class WormApp(App): def build(self): self.form = Form() self.form.start() return self.form if __name__ == '__main__': WormApp().run() 

рдХреЛрд╢рд┐рдХрд╛ рдЖрдХрд╛рд░ рдореЗрдВ рдЖ рдЬрд╛рдПрдЧреАред рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рд╣рдо рдШрдбрд╝реА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдХрд┐рд╕реА рднреА рдлрд╝рдВрдХреНрд╢рди рдХреЗ рд▓рд┐рдП рдПрдХ рдЯрд╛рдЗрдорд░ рд╕реЗрдЯ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред

рдЗрд╕рдХреЗ рдмрд╛рдж, рдПрдХ рдЯрдЪ рдИрд╡реЗрдВрдЯ рдмрдирд╛рдПрдВред рдкреБрдирд░реНрд▓реЗрдЦрди рдлреЙрд░реНрдо:

 class Form(Widget): def __init__(self): super().__init__() self.cells = [] def start(self): Clock.schedule_interval(self.update, 0.01) def update(self, _): for cell in self.cells: cell.pos = (cell.pos[0] + 2, cell.pos[1] + 3) def on_touch_down(self, touch): cell = Cell(touch.x, touch.y, 30) self.add_widget(cell) self.cells.append(cell) 

рдкреНрд░рддреНрдпреЗрдХ рдЯрдЪ_рдбрд╛рдЙрди рдирд┐рд░реНрджреЗрд╢рд╛рдВрдХ = (рдЯрдЪ.рдПрдХреНрд╕, рдЯрдЪ.рд╡рд╛рдИ) рдФрд░ рдЖрдХрд╛рд░ = 30 рдХреЗ рд╕рд╛рде рдПрдХ рд╕реЗрд▓ рдмрдирд╛рддрд╛ рд╣реИред рдлрд┐рд░, рд╣рдо рдЗрд╕реЗ рдЕрдкрдиреЗ рд╕реНрд╡рдпрдВ рдХреЗ рдРрд░реЗ рдХреЗ рд░реВрдк рдореЗрдВ рдФрд░ рдлреЙрд░реНрдо рд╡рд┐рдЬреЗрдЯ рдХреЗ рд░реВрдк рдореЗрдВ рдЬреЛрдбрд╝реЗрдВрдЧреЗ (рдмрд╛рдж рдореЗрдВ рдЗрд╕реЗ рдПрдХреНрд╕реЗрд╕ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП)ред

рдЕрдм рдлреЙрд░реНрдо рдкрд░ рдкреНрд░рддреНрдпреЗрдХ рдХреНрд▓рд┐рдХ рдПрдХ рд╕реЗрд▓ рдЙрддреНрдкрдиреНрди рдХрд░рддрд╛ рд╣реИред



рдЕрдЪреНрдЫреА рд╕реЗрдЯрд┐рдВрдЧреНрд╕


рдЪреВрдВрдХрд┐ рд╣рдо рдПрдХ рд╕реБрдВрджрд░ рд╕рд╛рдВрдк рдмрдирд╛рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ, рд╣рдореЗрдВ рддрд╛рд░реНрдХрд┐рдХ рд░реВрдк рд╕реЗ рдЧреНрд░рд╛рдлрд┐рдХ рдФрд░ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рд╕реНрдерд┐рддрд┐ рдХреЛ рдЕрд▓рдЧ рдХрд░рдирд╛ рд╣реЛрдЧрд╛ред

рдХреНрдпреЛрдВ?
рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рдХрдИ рдХрд╛рд░рдг рд╣реИрдВред рд╕рднреА рддрд░реНрдХ рддрдерд╛рдХрдерд┐рдд рд╡рд╛рд╕реНрддрд╡рд┐рдХ рд╕реНрдерд┐рддрд┐ рд╕реЗ рдЬреБрдбрд╝реЗ рд╣реЛрдиреЗ рдЪрд╛рд╣рд┐рдП, рд▓реЗрдХрд┐рди рдЧреНрд░рд╛рдлрд┐рдХ рдПрдХ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдкрд░рд┐рдгрд╛рдо рд╣реИред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдпрджрд┐ рд╣рдо рдЗрдВрдбреЗрдВрдЯ рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ, рддреЛ рд╡рд░реНрддрдорд╛рди рд╕реНрдерд┐рддрд┐ (100, 100) рд╣реЛрдЧреА рдЬрдмрдХрд┐ рдЧреНрд░рд╛рдлрд┐рдХ рд╕реНрдерд┐рддрд┐ (102, 102) рд╣реИред

PS рдпрджрд┐ рд╣рдо on_draw рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░ рд░рд╣реЗ рдереЗ рддреЛ рд╣рдо рднрд╛рдк рд╕реНрдирд╛рди рдирд╣реАрдВ рдХрд░реЗрдВрдЧреЗред рд▓реЗрдХрд┐рди рдЕрдм рд╣рдореЗрдВ рдкрдВрдЬреЗ рдХреЗ рд╕рд╛рде рдЖрдХрд╛рд░ рдХреЛ рдлрд┐рд░ рд╕реЗ рдирд╣реАрдВ рдмрдирд╛рдирд╛ рд╣реИред

рдЪрд▓реЛ worm.kv рдлрд╝рд╛рдЗрд▓ рдмрджрд▓реЗрдВ:

 <Form>: <Cell>: canvas: Rectangle: size: self.graphical_size pos: self.graphical_pos 

рдФрд░ рдореЗрдирд╣реЛрдо

 ... from kivy.properties import * ... class Cell(Widget): graphical_size = ListProperty([1, 1]) graphical_pos = ListProperty([1, 1]) def __init__(self, x, y, size, margin=4): super().__init__() self.actual_size = (size, size) self.graphical_size = (size - margin, size - margin) self.margin = margin self.actual_pos = (x, y) self.graphical_pos_attach() def graphical_pos_attach(self): self.graphical_pos = (self.actual_pos[0] - self.graphical_size[0] / 2, self.actual_pos[1] - self.graphical_size[1] / 2) ... class Form(Widget): def __init__(self): super().__init__() self.cell1 = Cell(100, 100, 30) self.cell2 = Cell(130, 100, 30) self.add_widget(self.cell1) self.add_widget(self.cell2) ... 



рдЗрдВрдбреЗрдВрдЯреЗрд╢рди рджрд┐рдЦрд╛рдИ рджрд┐рдпрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдпрд╣ рдЗрд╕ рддрдереНрдп рдХреЗ рдмрд╛рд╡рдЬреВрдж рдмреЗрд╣рддрд░ рд╣реИ рдХрд┐ рд╣рдордиреЗ 132 рдХреЗ рдмрдЬрд╛рдп рдПрдХреНрд╕ = 130 рдХреЗ рд╕рд╛рде рдПрдХ рджреВрд╕рд░рд╛ рд╕реЗрд▓ рдмрдирд╛рдпрд╛ред рдмрд╛рдж рдореЗрдВ рд╣рдо рд╡рд╛рд╕реНрддрд╡рд┐рдХ_рдкреЛрд╕ рдФрд░ рдЧреНрд░рд╛рдлрд┐рдХрд▓_рдкреЛрд╕ рдХреЗ рдмреАрдЪ рдХреА рджреВрд░реА рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рдПрдХ рдирд░рдо рдЖрдВрджреЛрд▓рди рдХрд░реЗрдВрдЧреЗред

рд╡рд░реНрдо рдкреНрд░реЛрдЧреНрд░рд╛рдорд┐рдВрдЧ


рд╡рд┐рдЬреНрдЮрд╛рдкрди


рдореЗрдирдлреНрд░реЗрдо рдореЗрдВ рдкреНрд░рд╛рд░рдВрднрд┐рдХ рд╡рд┐рдиреНрдпрд╛рд╕

 class Config: DEFAULT_LENGTH = 20 CELL_SIZE = 25 APPLE_SIZE = 35 MARGIN = 4 INTERVAL = 0.2 DEAD_CELL = (1, 0, 0, 1) APPLE_COLOR = (1, 1, 0, 1) 

(рдореЗрд░рд╛ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдХрд░реЛ, рдЖрдк рдЗрд╕реЗ рдкреНрдпрд╛рд░ рдХрд░реЗрдВрдЧреЗ!)

рдлрд┐рд░ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рдХреЙрдиреНтАНрдлрд╝рд┐рдЧрд░ рдХрд░реЗрдВ:

 class WormApp(App): def __init__(self): super().__init__() self.config = Config() self.form = Form(self.config) def build(self): self.form.start() return self.form 

рдкреБрдирдГ рдЖрд░рдВрдн рдХрд░реЗрдВ рдФрд░ рдкреНрд░рд╛рд░рдВрдн рдХрд░реЗрдВ:

 class Form(Widget): def __init__(self, config): super().__init__() self.config = config self.worm = None def start(self): self.worm = Worm(self.config) self.add_widget(self.worm) Clock.schedule_interval(self.update, self.config.INTERVAL) 

рдлрд┐рд░, рд╕реЗрд▓:

 class Cell(Widget): graphical_size = ListProperty([1, 1]) graphical_pos = ListProperty([1, 1]) def __init__(self, x, y, size, margin=4): super().__init__() self.actual_size = (size, size) self.graphical_size = (size - margin, size - margin) self.margin = margin self.actual_pos = (x, y) self.graphical_pos_attach() def graphical_pos_attach(self): self.graphical_pos = (self.actual_pos[0] - self.graphical_size[0] / 2, self.actual_pos[1] - self.graphical_size[1] / 2) def move_to(self, x, y): self.actual_pos = (x, y) self.graphical_pos_attach() def move_by(self, x, y, **kwargs): self.move_to(self.actual_pos[0] + x, self.actual_pos[1] + y, **kwargs) def get_pos(self): return self.actual_pos def step_by(self, direction, **kwargs): self.move_by(self.actual_size[0] * direction[0], self.actual_size[1] * direction[1], **kwargs) 

рдореБрдЭреЗ рдЙрдореНрдореАрдж рд╣реИ рдХрд┐ рдпрд╣ рдХрдореЛрдмреЗрд╢ рд╕реНрдкрд╖реНрдЯ рдерд╛ред

рдФрд░ рдЕрдВрдд рдореЗрдВ рдЗрд▓реНрд▓реА:

 class Worm(Widget): def __init__(self, config): super().__init__() self.cells = [] self.config = config self.cell_size = config.CELL_SIZE self.head_init((100, 100)) for i in range(config.DEFAULT_LENGTH): self.lengthen() def destroy(self): for i in range(len(self.cells)): self.remove_widget(self.cells[i]) self.cells = [] def lengthen(self, pos=None, direction=(0, 1)): #   ,    ,  -      if pos is None: px = self.cells[-1].get_pos()[0] + direction[0] * self.cell_size py = self.cells[-1].get_pos()[1] + direction[1] * self.cell_size pos = (px, py) self.cells.append(Cell(*pos, self.cell_size, margin=self.config.MARGIN)) self.add_widget(self.cells[-1]) def head_init(self, pos): self.lengthen(pos=pos) 

рдЪрд▓реЛ рд╣рдорд╛рд░рд╛ рдХреАрдбрд╝рд╛ рдмрдирд╛рдПрдБред



рдкреНрд░рд╕реНрддрд╛рд╡


рдЕрдм рд╣рдо рдЗрд╕реЗ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рддреЗ рд╣реИрдВред

рдпрд╣ рд╕рд░рд▓ рд╣реИ:

 class Worm(Widget): ... def move(self, direction): for i in range(len(self.cells) - 1, 0, -1): self.cells[i].move_to(*self.cells[i - 1].get_pos()) self.cells[0].step_by(direction) 

 class Form(Widget): def __init__(self, config): super().__init__() self.config = config self.worm = None self.cur_dir = (0, 0) def start(self): self.worm = Worm(self.config) self.add_widget(self.worm) self.cur_dir = (1, 0) Clock.schedule_interval(self.update, self.config.INTERVAL) def update(self, _): self.worm.move(self.cur_dir) 



рдпрд╣ рдЬреАрд╡рд┐рдд рд╣реИ! рдпрд╣ рдЬреАрд╡рд┐рдд рд╣реИ!

рдкреНрд░рдмрдВрдз


рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рдкрд╣рд▓реА рддрд╕реНрд╡реАрд░ рд╕реЗ рдЖрдВрдХ рд╕рдХрддреЗ рд╣реИрдВ, рд╕рд╛рдВрдк рдХрд╛ рдирд┐рдпрдВрддреНрд░рдг рдЗрд╕ рддрд░рд╣ рд╣реЛрдЧрд╛:



 class Form(Widget): ... def on_touch_down(self, touch): ws = touch.x / self.size[0] hs = touch.y / self.size[1] aws = 1 - ws if ws > hs and aws > hs: cur_dir = (0, -1) #  elif ws > hs >= aws: cur_dir = (1, 0) #  elif ws <= hs < aws: cur_dir = (-1, 0) #  else: cur_dir = (0, 1) #  self.cur_dir = cur_dir 



рд╡рд╛рд╣ред

рдлрд▓ рдмрдирд╛рдирд╛


рдкрд╣рд▓реА рдШреЛрд╖рдгрд╛ред

 class Form(Widget): ... def __init__(self, config): super().__init__() self.config = config self.worm = None self.cur_dir = (0, 0) self.fruit = None ... def random_cell_location(self, offset): x_row = self.size[0] // self.config.CELL_SIZE x_col = self.size[1] // self.config.CELL_SIZE return random.randint(offset, x_row - offset), random.randint(offset, x_col - offset) def random_location(self, offset): x_row, x_col = self.random_cell_location(offset) return self.config.CELL_SIZE * x_row, self.config.CELL_SIZE * x_col def fruit_dislocate(self): x, y = self.random_location(2) self.fruit.move_to(x, y) ... def start(self): self.fruit = Cell(0, 0, self.config.APPLE_SIZE, self.config.MARGIN) self.worm = Worm(self.config) self.fruit_dislocate() self.add_widget(self.worm) self.add_widget(self.fruit) self.cur_dir = (1, 0) Clock.schedule_interval(self.update, self.config.INTERVAL) 

рд╡рд░реНрддрдорд╛рди рдкрд░рд┐рдгрд╛рдо:



рдЕрдм рд╣рдореЗрдВ рдХрдИ рдХреГрдорд┐ рд╡рд┐рдзрд┐ рдШреЛрд╖рд┐рдд рдХрд░рдиреА рд╣реЛрдЧреА:

 class Worm(Widget): ... #      def gather_positions(self): return [cell.get_pos() for cell in self.cells] #        def head_intersect(self, cell): return self.cells[0].get_pos() == cell.get_pos() 

рдЗрдХрдЯреНрдард╛_ рд╕рдорд╛рд░реЛрд╣ рдХреЗ рдЕрдиреНрдп рдмреЛрдирд╕
рд╡реИрд╕реЗ, рдЬрдм рд╣рдордиреЗ рдЗрдХрдЯреНрдард╛ рд╣реЛрдиреЗ рдХреА рдШреЛрд╖рдгрд╛ рдХреА, рддреЛ рд╣рдо fruit_dislocate рдореЗрдВ рд╕реБрдзрд╛рд░ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:

 class Form(Widget): def fruit_dislocate(self): x, y = self.random_location(2) while (x, y) in self.worm.gather_positions(): x, y = self.random_location(2) self.fruit.move_to(x, y) 

рдЗрд╕ рдмрд┐рдВрджреБ рдкрд░, рд╕реЗрдм рдХреА рд╕реНрдерд┐рддрд┐ рдкреВрдВрдЫ рдХреА рд╕реНрдерд┐рддрд┐ рд╕реЗ рдореЗрд▓ рдирд╣реАрдВ рдЦрд╛ рд╕рдХрддреА рд╣реИ

... рдФрд░ рдЕрдкрдбреЗрдЯ рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдП рдЪреЗрдХ ()

 class Form(Widget): ... def update(self, _): self.worm.move(self.cur_dir) if self.worm.head_intersect(self.fruit): directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] self.worm.lengthen(direction=random.choice(directions)) self.fruit_dislocate() 

рд╕рд┐рд░ рдФрд░ рдкреВрдВрдЫ рдХреЗ рдЪреМрд░рд╛рд╣реЗ рдХрд╛ рдирд┐рд░реНрдзрд╛рд░рдг


рд╣рдо рдпрд╣ рдкрддрд╛ рд▓рдЧрд╛рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ рдХрд┐ рдХреНрдпрд╛ рд╕рд┐рд░ рдХреА рд╕реНрдерд┐рддрд┐ рдХреБрдЫ рдЯреЗрд▓ рд╕реЗрд▓ рдХреЗ рд╕рдорд╛рди рд╣реИред

 class Form(Widget): ... def __init__(self, config): super().__init__() self.config = config self.worm = None self.cur_dir = (0, 0) self.fruit = None self.game_on = True def update(self, _): if not self.game_on: return self.worm.move(self.cur_dir) if self.worm.head_intersect(self.fruit): directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] self.worm.lengthen(direction=random.choice(directions)) self.fruit_dislocate() if self.worm_bite_self(): self.game_on = False def worm_bite_self(self): for cell in self.worm.cells[1:]: if self.worm.head_intersect(cell): return cell return False 



рд░рдВрдЧ, рд╕рдЬрд╛рдиреЗ, рдХреЛрдб refactoring


рдЖрдЗрдП рд░рд┐рдлреНрд▓реЗрдХреНрдЯрд┐рдВрдЧ рдХреЗ рд╕рд╛рде рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВред

рдлрд┐рд░ рд╕реЗ рд▓рд┐рдЦрдирд╛ рдФрд░ рдЬреЛрдбрд╝рдирд╛

 class Form(Widget): ... def start(self): self.worm = Worm(self.config) self.add_widget(self.worm) if self.fruit is not None: self.remove_widget(self.fruit) self.fruit = Cell(0, 0, self.config.APPLE_SIZE) self.fruit_dislocate() self.add_widget(self.fruit) Clock.schedule_interval(self.update, self.config.INTERVAL) self.game_on = True self.cur_dir = (0, -1) def stop(self): self.game_on = False Clock.unschedule(self.update) def game_over(self): self.stop() ... def on_touch_down(self, touch): if not self.game_on: self.worm.destroy() self.start() return ... 

рдЕрдм рдЕрдЧрд░ рдХреАрдбрд╝рд╛ рдорд░ рдЧрдпрд╛ (рдЬрдо рдЧрдпрд╛), рдЕрдЧрд░ рдЖрдк рд╕реНрдХреНрд░реАрди рдкрд░ рдХреНрд▓рд┐рдХ рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рдЧреЗрдо рдлрд┐рд░ рд╕реЗ рд╢реБрд░реВ рд╣реЛ рдЬрд╛рдПрдЧрд╛ред

рдЕрдм рдЪрд▓реЛ рд╕рдЬрд╛рдиреЗ рдФрд░ рд░рдВрдЧ рджреЗрдиреЗ рдХреЗ рд▓рд┐рдП рдЖрдЧреЗ рдмрдврд╝рддреЗ рд╣реИрдВред

worm.kv

 <Form>: popup_label: popup_label score_label: score_label canvas: Color: rgba: (.5, .5, .5, 1.0) Line: width: 1.5 points: (0, 0), self.size Line: width: 1.5 points: (self.size[0], 0), (0, self.size[1]) Label: id: score_label text: "Score: " + str(self.parent.worm_len) width: self.width Label: id: popup_label width: self.width <Worm>: <Cell>: canvas: Color: rgba: self.color Rectangle: size: self.graphical_size pos: self.graphical_pos 

рд╣рдо WormApp рдХреЛ рдлрд┐рд░ рд╕реЗ рд▓рд┐рдЦрддреЗ рд╣реИрдВ:

 class WormApp(App): def build(self): self.config = Config() self.form = Form(self.config) return self.form def on_start(self): self.form.start() 



рд╣рдо рд░рдВрдЧред рд░реАрдХреНрд░рд┐рдПрдЯ рд╕реЗрд▓ .kv:

 <Cell>: canvas: Color: rgba: self.color Rectangle: size: self.graphical_size pos: self.graphical_pos 


рдЗрд╕реЗ рд╕реЗрд▓ .__ init__ рдореЗрдВ рдЬреЛрдбрд╝реЗрдВ:
 self.color = (0.2, 1.0, 0.2, 1.0) # 

рдФрд░ рдпрд╣ Form.start рдХреЛ рд╣реИ

 self.fruit.color = (1.0, 0.2, 0.2, 1.0) 

рд╢рд╛рдирджрд╛рд░, рд╕рд╛рдБрдк рдХрд╛ рдЖрдирдВрдж рд▓реЗрдВ



рдЕрдВрдд рдореЗрдВ, рд╣рдо рд╢рд┐рд▓рд╛рд▓реЗрдЦ "рдЦреЗрд▓ рдЦрддреНрдо" рдмрдирд╛рдПрдВрдЧреЗ

 class Form(Widget): ... def __init__(self, config): ... self.popup_label.text = "" ... def stop(self, text=""): self.game_on = False self.popup_label.text = text Clock.unschedule(self.update) def game_over(self): self.stop("GAME OVER" + " " * 5 + "\ntap to reset") 

рдФрд░ "рдШрд╛рдпрд▓" рд╕реЗрд▓ рдХреЛ рд▓рд╛рд▓ рд░рдВрдЧ рдореЗрдВ рд╕реЗрдЯ рдХрд░реЗрдВ:

рдХреЗ рдмрдЬрд╛рдп

  def update(self, _): ... if self.worm_bite_self(): self.game_over() ... 


рд▓рд┐рдЦрдиреЗ

  def update(self, _): cell = self.worm_bite_self() if cell: cell.color = (1.0, 0.2, 0.2, 1.0) self.game_over() 



рдХреНрдпрд╛ рдЖрдк рдЕрднреА рднреА рдпрд╣рд╛рдБ рд╣реИрдВ рд╕рдмрд╕реЗ рджрд┐рд▓рдЪрд╕реНрдк рд╣рд┐рд╕реНрд╕рд╛ рдЖрдЧреЗ рд╣реИ!

рдмреЛрдирд╕ - рдЪрд┐рдХрдиреА рдЖрдВрджреЛрд▓рди


рдЪреВрдВрдХрд┐ рдХреАрдбрд╝рд╛ рдХрджрдо cell_size рдХреЗ рдмрд░рд╛рдмрд░ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдпрд╣ рдмрд╣реБрдд рдЪрд┐рдХрдирд╛ рдирд╣реАрдВ рджрд┐рдЦрддрд╛ рд╣реИред рд▓реЗрдХрд┐рди рд╣рдо рдкреВрд░реА рддрд░рд╣ рд╕реЗ рдЦреЗрд▓ рддрд░реНрдХ рдХреЛ рдкреВрд░реА рддрд░рд╣ рд╕реЗ рдлрд┐рд░ рд╕реЗ рд▓рд┐рдЦреЗ рдмрд┐рдирд╛ рд╕рдВрднрд╡ рдХреЗ рд░реВрдк рдореЗрдВ рдХрджрдо рд░рдЦрдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВред рдЗрд╕ рдкреНрд░рдХрд╛рд░, рд╣рдореЗрдВ рдПрдХ рдРрд╕реЗ рддрдВрддреНрд░ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рдЬреЛ рд╣рдорд╛рд░реЗ рдЧреНрд░рд╛рдлрд┐рдХрд▓ рдкрджреЛрдВ (рдЧреНрд░рд╛рдлрд┐рдХрд▓_рдкреЛрд╕) рдХреЛ рдЖрдЧреЗ рдмрдврд╝рд╛рдП рд▓реЗрдХрд┐рди рд╡рд╛рд╕реНрддрд╡рд┐рдХ рд▓реЛрдЧреЛрдВ (рд╡рд╛рд╕реНрддрд╡рд┐рдХ_рд░реВрдк) рдХреЛ рдкреНрд░рднрд╛рд╡рд┐рдд рди рдХрд░реЗред рдореИрдВрдиреЗ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдХреЛрдб рд▓рд┐рдЦрд╛ рд╣реИ:

smooth.py

 from kivy.clock import Clock import time class Timing: @staticmethod def linear(x): return x class Smooth: def __init__(self, interval=1.0/60.0): self.objs = [] self.running = False self.interval = interval def run(self): if self.running: return self.running = True Clock.schedule_interval(self.update, self.interval) def stop(self): if not self.running: return self.running = False Clock.unschedule(self.update) def setattr(self, obj, attr, value): exec("obj." + attr + " = " + str(value)) def getattr(self, obj, attr): return float(eval("obj." + attr)) def update(self, _): cur_time = time.time() for line in self.objs: obj, prop_name_x, prop_name_y, from_x, from_y, to_x, to_y, start_time, period, timing = line time_gone = cur_time - start_time if time_gone >= period: self.setattr(obj, prop_name_x, to_x) self.setattr(obj, prop_name_y, to_y) self.objs.remove(line) else: share = time_gone / period acs = timing(share) self.setattr(obj, prop_name_x, from_x * (1 - acs) + to_x * acs) self.setattr(obj, prop_name_y, from_y * (1 - acs) + to_y * acs) if len(self.objs) == 0: self.stop() def move_to(self, obj, prop_name_x, prop_name_y, to_x, to_y, t, timing=Timing.linear): self.objs.append((obj, prop_name_x, prop_name_y, self.getattr(obj, prop_name_x), self.getattr(obj, prop_name_y), to_x, to_y, time.time(), t, timing)) self.run() class XSmooth(Smooth): def __init__(self, props, timing=Timing.linear, *args, **kwargs): super().__init__(*args, **kwargs) self.props = props self.timing = timing def move_to(self, obj, to_x, to_y, t): super().move_to(obj, *self.props, to_x, to_y, t, timing=self.timing) 

рдЬрд┐рдиреНрд╣реЗрдВ рдпрд╣ рдХреЛрдб рдкрд╕рдВрдж рдирд╣реАрдВ рдЖрдпрд╛
рдпрд╣ рдореЙрдбреНрдпреВрд▓ рд▓рд╛рд▓рд┐рддреНрдп рдХрд╛ рд╢реАрд░реНрд╖ рдирд╣реАрдВ рд╣реИред рдореИрдВ рдЗрд╕ рдлреИрд╕рд▓реЗ рдХреЛ рдмреБрд░рд╛ рдорд╛рдирддрд╛ рд╣реВрдВред рд▓реЗрдХрд┐рди рдпрд╣ рдХреЗрд╡рд▓ рдПрдХ рд╣реИрд▓реЛ-рд╡рд░реНрд▓реНрдб рд╕рдорд╛рдзрд╛рди рд╣реИред

рддреЛ, рдЖрдк рдмрд╕ smooth.py рдмрдирд╛рдПрдБ рдФрд░ рдлрд╝рд╛рдЗрд▓ рдореЗрдВ рдХреЛрдб рдХреЙрдкреА рдХрд░реЗрдВред
рдЕрдВрдд рдореЗрдВ, рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрдИрдЯреА рдкреНрд░рд╛рдкреНрдд рдХрд░реЗрдВ!

 class Form(Widget): ... def __init__(self, config): ... self.smooth = smooth.XSmooth(["graphical_pos[0]", "graphical_pos[1]"]) 

рд╕реЗрд▓реНрдлреА рдмрджрд▓реЗрдВ

 class Form(Widget): ... def update(self, _): ... self.worm.move(self.cur_dir, smooth_motion=(self.smooth, self.config.INTERVAL)) 

рдФрд░ рдЗрд╕ рддрд░рд╣ рд╕реЗ рд╕реЗрд▓ рдХреЗ рддрд░реАрдХреЛрдВ рдХреЛ рджреЗрдЦрдирд╛ рдЪрд╛рд╣рд┐рдП

 class Cell(Widget): ... def graphical_pos_attach(self, smooth_motion=None): to_x, to_y = self.actual_pos[0] - self.graphical_size[0] / 2, self.actual_pos[1] - self.graphical_size[1] / 2 if smooth_motion is None: self.graphical_pos = to_x, to_y else: smoother, t = smooth_motion smoother.move_to(self, to_x, to_y, t) def move_to(self, x, y, **kwargs): self.actual_pos = (x, y) self.graphical_pos_attach(**kwargs) def move_by(self, x, y, **kwargs): self.move_to(self.actual_pos[0] + x, self.actual_pos[1] + y, **kwargs) 

рдЦреИрд░, рдпрд╣ рддреЛ рд╣реИ, рдЖрдкрдХреЗ рдзреНрдпрд╛рди рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рдж! рдХреЛрдб рдиреАрдЪреЗ рд╣реИред

рдбреЗрдореЛ рд╡реАрдбрд┐рдпреЛ рдХреИрд╕реЗ рдкрд░рд┐рдгрд╛рдо рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ:


рдЕрдВрддрд┐рдо рдХреЛрдб
main.py
 from kivy.app import App from kivy.uix.widget import Widget from kivy.clock import Clock from kivy.properties import * import random import smooth class Cell(Widget): graphical_size = ListProperty([1, 1]) graphical_pos = ListProperty([1, 1]) color = ListProperty([1, 1, 1, 1]) def __init__(self, x, y, size, margin=4): super().__init__() self.actual_size = (size, size) self.graphical_size = (size - margin, size - margin) self.margin = margin self.actual_pos = (x, y) self.graphical_pos_attach() self.color = (0.2, 1.0, 0.2, 1.0) def graphical_pos_attach(self, smooth_motion=None): to_x, to_y = self.actual_pos[0] - self.graphical_size[0] / 2, self.actual_pos[1] - self.graphical_size[1] / 2 if smooth_motion is None: self.graphical_pos = to_x, to_y else: smoother, t = smooth_motion smoother.move_to(self, to_x, to_y, t) def move_to(self, x, y, **kwargs): self.actual_pos = (x, y) self.graphical_pos_attach(**kwargs) def move_by(self, x, y, **kwargs): self.move_to(self.actual_pos[0] + x, self.actual_pos[1] + y, **kwargs) def get_pos(self): return self.actual_pos def step_by(self, direction, **kwargs): self.move_by(self.actual_size[0] * direction[0], self.actual_size[1] * direction[1], **kwargs) class Worm(Widget): def __init__(self, config): super().__init__() self.cells = [] self.config = config self.cell_size = config.CELL_SIZE self.head_init((100, 100)) for i in range(config.DEFAULT_LENGTH): self.lengthen() def destroy(self): for i in range(len(self.cells)): self.remove_widget(self.cells[i]) self.cells = [] def lengthen(self, pos=None, direction=(0, 1)): if pos is None: px = self.cells[-1].get_pos()[0] + direction[0] * self.cell_size py = self.cells[-1].get_pos()[1] + direction[1] * self.cell_size pos = (px, py) self.cells.append(Cell(*pos, self.cell_size, margin=self.config.MARGIN)) self.add_widget(self.cells[-1]) def head_init(self, pos): self.lengthen(pos=pos) def move(self, direction, **kwargs): for i in range(len(self.cells) - 1, 0, -1): self.cells[i].move_to(*self.cells[i - 1].get_pos(), **kwargs) self.cells[0].step_by(direction, **kwargs) def gather_positions(self): return [cell.get_pos() for cell in self.cells] def head_intersect(self, cell): return self.cells[0].get_pos() == cell.get_pos() class Form(Widget): worm_len = NumericProperty(0) def __init__(self, config): super().__init__() self.config = config self.worm = None self.cur_dir = (0, 0) self.fruit = None self.game_on = True self.smooth = smooth.XSmooth(["graphical_pos[0]", "graphical_pos[1]"]) def random_cell_location(self, offset): x_row = self.size[0] // self.config.CELL_SIZE x_col = self.size[1] // self.config.CELL_SIZE return random.randint(offset, x_row - offset), random.randint(offset, x_col - offset) def random_location(self, offset): x_row, x_col = self.random_cell_location(offset) return self.config.CELL_SIZE * x_row, self.config.CELL_SIZE * x_col def fruit_dislocate(self): x, y = self.random_location(2) while (x, y) in self.worm.gather_positions(): x, y = self.random_location(2) self.fruit.move_to(x, y) def start(self): self.worm = Worm(self.config) self.add_widget(self.worm) if self.fruit is not None: self.remove_widget(self.fruit) self.fruit = Cell(0, 0, self.config.APPLE_SIZE) self.fruit.color = (1.0, 0.2, 0.2, 1.0) self.fruit_dislocate() self.add_widget(self.fruit) self.game_on = True self.cur_dir = (0, -1) Clock.schedule_interval(self.update, self.config.INTERVAL) self.popup_label.text = "" def stop(self, text=""): self.game_on = False self.popup_label.text = text Clock.unschedule(self.update) def game_over(self): self.stop("GAME OVER" + " " * 5 + "\ntap to reset") def align_labels(self): try: self.popup_label.pos = ((self.size[0] - self.popup_label.width) / 2, self.size[1] / 2) self.score_label.pos = ((self.size[0] - self.score_label.width) / 2, self.size[1] - 80) except: print(self.__dict__) assert False def update(self, _): if not self.game_on: return self.worm.move(self.cur_dir, smooth_motion=(self.smooth, self.config.INTERVAL)) if self.worm.head_intersect(self.fruit): directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] self.worm.lengthen(direction=random.choice(directions)) self.fruit_dislocate() cell = self.worm_bite_self() if cell: cell.color = (1.0, 0.2, 0.2, 1.0) self.game_over() self.worm_len = len(self.worm.cells) self.align_labels() def on_touch_down(self, touch): if not self.game_on: self.worm.destroy() self.start() return ws = touch.x / self.size[0] hs = touch.y / self.size[1] aws = 1 - ws if ws > hs and aws > hs: cur_dir = (0, -1) elif ws > hs >= aws: cur_dir = (1, 0) elif ws <= hs < aws: cur_dir = (-1, 0) else: cur_dir = (0, 1) self.cur_dir = cur_dir def worm_bite_self(self): for cell in self.worm.cells[1:]: if self.worm.head_intersect(cell): return cell return False class Config: DEFAULT_LENGTH = 20 CELL_SIZE = 25 APPLE_SIZE = 35 MARGIN = 4 INTERVAL = 0.3 DEAD_CELL = (1, 0, 0, 1) APPLE_COLOR = (1, 1, 0, 1) class WormApp(App): def build(self): self.config = Config() self.form = Form(self.config) return self.form def on_start(self): self.form.start() if __name__ == '__main__': WormApp().run() 



smooth.py
 from kivy.clock import Clock import time class Timing: @staticmethod def linear(x): return x class Smooth: def __init__(self, interval=1.0/60.0): self.objs = [] self.running = False self.interval = interval def run(self): if self.running: return self.running = True Clock.schedule_interval(self.update, self.interval) def stop(self): if not self.running: return self.running = False Clock.unschedule(self.update) def setattr(self, obj, attr, value): exec("obj." + attr + " = " + str(value)) def getattr(self, obj, attr): return float(eval("obj." + attr)) def update(self, _): cur_time = time.time() for line in self.objs: obj, prop_name_x, prop_name_y, from_x, from_y, to_x, to_y, start_time, period, timing = line time_gone = cur_time - start_time if time_gone >= period: self.setattr(obj, prop_name_x, to_x) self.setattr(obj, prop_name_y, to_y) self.objs.remove(line) else: share = time_gone / period acs = timing(share) self.setattr(obj, prop_name_x, from_x * (1 - acs) + to_x * acs) self.setattr(obj, prop_name_y, from_y * (1 - acs) + to_y * acs) if len(self.objs) == 0: self.stop() def move_to(self, obj, prop_name_x, prop_name_y, to_x, to_y, t, timing=Timing.linear): self.objs.append((obj, prop_name_x, prop_name_y, self.getattr(obj, prop_name_x), self.getattr(obj, prop_name_y), to_x, to_y, time.time(), t, timing)) self.run() class XSmooth(Smooth): def __init__(self, props, timing=Timing.linear, *args, **kwargs): super().__init__(*args, **kwargs) self.props = props self.timing = timing def move_to(self, obj, to_x, to_y, t): super().move_to(obj, *self.props, to_x, to_y, t, timing=self.timing) 



worm.kv
 <Form>: popup_label: popup_label score_label: score_label canvas: Color: rgba: (.5, .5, .5, 1.0) Line: width: 1.5 points: (0, 0), self.size Line: width: 1.5 points: (self.size[0], 0), (0, self.size[1]) Label: id: score_label text: "Score: " + str(self.parent.worm_len) width: self.width Label: id: popup_label width: self.width <Worm>: <Cell>: canvas: Color: rgba: self.color Rectangle: size: self.graphical_size pos: self.graphical_pos 




рдХреЛрдб @tshirtman рджреНрд╡рд╛рд░рд╛ рдереЛрдбрд╝рд╛ рд╕рдВрд╢реЛрдзрд┐рдд
рдХрд┐рд╡реА рдпреЛрдЧрджрд╛рдирдХрд░реНрддрд╛рдУрдВ рдореЗрдВ рд╕реЗ рдПрдХ рдЯреАрд╢рд░реНрдЯрдореИрди рджреНрд╡рд╛рд░рд╛ рдореЗрд░рд╛ рдХреЛрдб рдЪреЗрдХ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛, рдЬрд┐рдиреНрд╣реЛрдВрдиреЗ рд╕реБрдЭрд╛рд╡ рджрд┐рдпрд╛ рдерд╛ рдХрд┐ рдореИрдВ рдкреНрд░рддреНрдпреЗрдХ рд╕реЗрд▓ рдХреЗ рд▓рд┐рдП рдПрдХ рд╡рд┐рдЬреЗрдЯ рдмрдирд╛рдиреЗ рдХреЗ рдмрдЬрд╛рдп рдкреНрд╡рд╛рдЗрдВрдЯ рдирд┐рд░реНрджреЗрд╢ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реВрдВред рд╣рд╛рд▓рд╛рдБрдХрд┐, рдЗрд╕ рдХреЛрдб рдХреЛ рдореЗрд░реЗ рд▓рд┐рдП рд╕рдордЭрдирд╛ рдЖрд╕рд╛рди рдирд╣реАрдВ рд╣реИ, рд╣рд╛рд▓рд╛рдВрдХрд┐ рдпрд╣ рдпреВрдЖрдИ рдФрд░ gamedev рдХреЗ рд╡рд┐рдХрд╛рд╕ рдХреЛ рд╕рдордЭрдиреЗ рдореЗрдВ рдирд┐рд╢реНрдЪрд┐рдд рд░реВрдк рд╕реЗ рдмреЗрд╣рддрд░ рд╣реИред рд╕рд╛рдорд╛рдиреНрдп рддреМрд░ рдкрд░, рдпрд╣рд╛рдВ рдХреЛрдб рд╣реИ:
main.py
 from kivy.app import App from kivy.uix.widget import Widget from kivy.clock import Clock from kivy.properties import * import random import smooth class Cell: def __init__(self, x, y): self.actual_pos = (x, y) def move_to(self, x, y): self.actual_pos = (x, y) def move_by(self, x, y): self.move_to(self.actual_pos[0] + x, self.actual_pos[1] + y) def get_pos(self): return self.actual_pos class Fruit(Cell): def __init__(self, x, y): super().__init__(x, y) class Worm(Widget): margin = NumericProperty(4) graphical_poses = ListProperty() inj_pos = ListProperty([-1000, -1000]) graphical_size = NumericProperty(0) def __init__(self, config, **kwargs): super().__init__(**kwargs) self.cells = [] self.config = config self.cell_size = config.CELL_SIZE self.head_init((self.config.CELL_SIZE * random.randint(3, 5), self.config.CELL_SIZE * random.randint(3, 5))) self.margin = config.MARGIN self.graphical_size = self.cell_size - self.margin for i in range(config.DEFAULT_LENGTH): self.lengthen() def destroy(self): self.cells = [] self.graphical_poses = [] self.inj_pos = [-1000, -1000] def cell_append(self, pos): self.cells.append(Cell(*pos)) self.graphical_poses.extend([0, 0]) self.cell_move_to(len(self.cells) - 1, pos) def lengthen(self, pos=None, direction=(0, 1)): if pos is None: px = self.cells[-1].get_pos()[0] + direction[0] * self.cell_size py = self.cells[-1].get_pos()[1] + direction[1] * self.cell_size pos = (px, py) self.cell_append(pos) def head_init(self, pos): self.lengthen(pos=pos) def cell_move_to(self, i, pos, smooth_motion=None): self.cells[i].move_to(*pos) to_x, to_y = pos[0], pos[1] if smooth_motion is None: self.graphical_poses[i * 2], self.graphical_poses[i * 2 + 1] = to_x, to_y else: smoother, t = smooth_motion smoother.move_to(self, "graphical_poses[" + str(i * 2) + "]", "graphical_poses[" + str(i * 2 + 1) + "]", to_x, to_y, t) def move(self, direction, **kwargs): for i in range(len(self.cells) - 1, 0, -1): self.cell_move_to(i, self.cells[i - 1].get_pos(), **kwargs) self.cell_move_to(0, (self.cells[0].get_pos()[0] + self.cell_size * direction[0], self.cells[0].get_pos()[1] + self.cell_size * direction[1]), **kwargs) def gather_positions(self): return [cell.get_pos() for cell in self.cells] def head_intersect(self, cell): return self.cells[0].get_pos() == cell.get_pos() class Form(Widget): worm_len = NumericProperty(0) fruit_pos = ListProperty([0, 0]) fruit_size = NumericProperty(0) def __init__(self, config, **kwargs): super().__init__(**kwargs) self.config = config self.worm = None self.cur_dir = (0, 0) self.fruit = None self.game_on = True self.smooth = smooth.Smooth() def random_cell_location(self, offset): x_row = self.size[0] // self.config.CELL_SIZE x_col = self.size[1] // self.config.CELL_SIZE return random.randint(offset, x_row - offset), random.randint(offset, x_col - offset) def random_location(self, offset): x_row, x_col = self.random_cell_location(offset) return self.config.CELL_SIZE * x_row, self.config.CELL_SIZE * x_col def fruit_dislocate(self, xy=None): if xy is not None: x, y = xy else: x, y = self.random_location(2) while (x, y) in self.worm.gather_positions(): x, y = self.random_location(2) self.fruit.move_to(x, y) self.fruit_pos = (x, y) def start(self): self.worm = Worm(self.config) self.add_widget(self.worm) self.fruit = Fruit(0, 0) self.fruit_size = self.config.APPLE_SIZE self.fruit_dislocate() self.game_on = True self.cur_dir = (0, -1) Clock.schedule_interval(self.update, self.config.INTERVAL) self.popup_label.text = "" def stop(self, text=""): self.game_on = False self.popup_label.text = text Clock.unschedule(self.update) def game_over(self): self.stop("GAME OVER" + " " * 5 + "\ntap to reset") def align_labels(self): self.popup_label.pos = ((self.size[0] - self.popup_label.width) / 2, self.size[1] / 2) self.score_label.pos = ((self.size[0] - self.score_label.width) / 2, self.size[1] - 80) def update(self, _): if not self.game_on: return self.worm.move(self.cur_dir, smooth_motion=(self.smooth, self.config.INTERVAL)) if self.worm.head_intersect(self.fruit): directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] self.worm.lengthen(direction=random.choice(directions)) self.fruit_dislocate() cell = self.worm_bite_self() if cell is not None: self.worm.inj_pos = cell.get_pos() self.game_over() self.worm_len = len(self.worm.cells) self.align_labels() def on_touch_down(self, touch): if not self.game_on: self.worm.destroy() self.start() return ws = touch.x / self.size[0] hs = touch.y / self.size[1] aws = 1 - ws if ws > hs and aws > hs: cur_dir = (0, -1) elif ws > hs >= aws: cur_dir = (1, 0) elif ws <= hs < aws: cur_dir = (-1, 0) else: cur_dir = (0, 1) self.cur_dir = cur_dir def worm_bite_self(self): for cell in self.worm.cells[1:]: if self.worm.head_intersect(cell): return cell return None class Config: DEFAULT_LENGTH = 20 CELL_SIZE = 26 #  ,  CELL_SIZE - MARGIN    4 APPLE_SIZE = 36 MARGIN = 2 INTERVAL = 0.3 DEAD_CELL = (1, 0, 0, 1) APPLE_COLOR = (1, 1, 0, 1) class WormApp(App): def __init__(self, **kwargs): super().__init__(**kwargs) self.form = None def build(self, **kwargs): self.config = Config() self.form = Form(self.config, **kwargs) return self.form def on_start(self): self.form.start() if __name__ == '__main__': WormApp().run() 


smooth.py
 from kivy.clock import Clock import time class Timing: @staticmethod def linear(x): return x class Smooth: def __init__(self, interval=1.0/60.0): self.objs = [] self.running = False self.interval = interval def run(self): if self.running: return self.running = True Clock.schedule_interval(self.update, self.interval) def stop(self): if not self.running: return self.running = False Clock.unschedule(self.update) def set_attr(self, obj, attr, value): exec("obj." + attr + " = " + str(value)) def get_attr(self, obj, attr): return float(eval("obj." + attr)) def update(self, _): cur_time = time.time() for line in self.objs: obj, prop_name_x, prop_name_y, from_x, from_y, to_x, to_y, start_time, period, timing = line time_gone = cur_time - start_time if time_gone >= period: self.set_attr(obj, prop_name_x, to_x) self.set_attr(obj, prop_name_y, to_y) self.objs.remove(line) else: share = time_gone / period acs = timing(share) self.set_attr(obj, prop_name_x, from_x * (1 - acs) + to_x * acs) self.set_attr(obj, prop_name_y, from_y * (1 - acs) + to_y * acs) if len(self.objs) == 0: self.stop() def move_to(self, obj, prop_name_x, prop_name_y, to_x, to_y, t, timing=Timing.linear): self.objs.append((obj, prop_name_x, prop_name_y, self.get_attr(obj, prop_name_x), self.get_attr(obj, prop_name_y), to_x, to_y, time.time(), t, timing)) self.run() class XSmooth(Smooth): def __init__(self, props, timing=Timing.linear, *args, **kwargs): super().__init__(*args, **kwargs) self.props = props self.timing = timing def move_to(self, obj, to_x, to_y, t): super().move_to(obj, *self.props, to_x, to_y, t, timing=self.timing) 


worm.kv
 <Form>: popup_label: popup_label score_label: score_label canvas: Color: rgba: (.5, .5, .5, 1.0) Line: width: 1.5 points: (0, 0), self.size Line: width: 1.5 points: (self.size[0], 0), (0, self.size[1]) Color: rgba: (1.0, 0.2, 0.2, 1.0) Point: points: self.fruit_pos pointsize: self.fruit_size / 2 Label: id: score_label text: "Score: " + str(self.parent.worm_len) width: self.width Label: id: popup_label width: self.width <Worm>: canvas: Color: rgba: (0.2, 1.0, 0.2, 1.0) Point: points: self.graphical_poses pointsize: self.graphical_size / 2 Color: rgba: (1.0, 0.2, 0.2, 1.0) Point: points: self.inj_pos pointsize: self.graphical_size / 2 



рдХреЛрдИ рднреА рдкреНрд░рд╢реНрди рдкреВрдЫреЗрдВред

Source: https://habr.com/ru/post/hi466249/


All Articles