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

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

рдХреЛрд╢рд┐рдХрд╛рдУрдВ рдХреЗ рдмреАрдЪ рдХреЛрдИ рд╕реНрдерд╛рди рдирд╣реАрдВ рд╣реИ, рддреНрд░рд┐рдХреЛрдг рд╢рд░реНрдордирд╛рдХ рд╣реИ, рдЧреНрд░рд╛рдлрд┐рдХреНрд╕ рдЧрдбрд╝рдмрдбрд╝ рд╣реИрдВред
рдкрд░рд┐рдЪрд┐рдд рд╣реЛ рдЬрд╛рдУ
рдкрд╣рд▓рд╛ рдРрдк
рдХреГрдкрдпрд╛, рдкреБрд╖реНрдЯрд┐ рдХрд░реЗрдВ рдХрд┐ рдЖрдкрдиреЗ рдкрд╣рд▓реЗ рд╣реА рдХреАрд╡реА рдХреЛ рд╕реНрдерд╛рдкрд┐рдд рдХрд┐рдпрд╛ рд╣реИ (рдпрджрд┐ рдирд╣реАрдВ,
рдирд┐рд░реНрджреЗрд╢реЛрдВ рдХрд╛ рдкрд╛рд▓рди
рдХрд░реЗрдВ ) рдФрд░ рднрд╛рдЧрд╛
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()

рд╣рдордиреЗ рдПрдХ рд╡рд┐рдЬреЗрдЯ рдмрдирд╛рдпрд╛ред рдЕрдиреБрд░реВрдкрддрд╛ рд╕реЗ, рд╣рдо рдПрдХ рдмрдЯрди рдпрд╛ рдХреЛрдИ рдЕрдиреНрдп UI рддрддреНрд╡ рдмрдирд╛ рд╕рдХрддреЗ рд╣реИрдВ:
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 рдлрд╛рдЗрд▓реЗрдВ
рд╣рд╛рд▓рд╛рдБрдХрд┐, UI рддрддреНрд╡ рдмрдирд╛рдиреЗ рдХрд╛ рдПрдХ рдФрд░ рддрд░реАрдХрд╛ рд╣реИред рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рд╣рдо рдЕрдкрдирд╛ рдлреЙрд░реНрдо рд▓рд╛рдЧреВ рдХрд░рддреЗ рд╣реИрдВ:
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
рд╣рдордиреЗ рдЖрдпрдд рдХреА рд╕реНрдерд┐рддрд┐ рдХреЛ рд╕реНрд╡.рдкреЛрд╕ рдФрд░ рдЙрд╕рдХреЗ рдЖрдХрд╛рд░ рдХреЛ рд╕реНрд╡.рд╕рд╛рдЗрдЬрд╝ рд╕реЗ рдЬреЛрдбрд╝рд╛ред рдЗрд╕рд▓рд┐рдП рдЕрдм рд╡реЗ рдЧреБрдг рд╕реЗрд▓ рд╕реЗ рдЙрдкрд▓рдмреНрдз рд╣реИрдВ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдПрдХ рдмрд╛рд░ рд╕реЗрд▓ рдмрдирд╛рдиреЗ рдХреЗ рдмрд╛рдж, рд╣рдо рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ
class Cell(Widget): def __init__(self, x, y, size): super().__init__() self.size = (size, size)

рдареАрдХ рд╣реИ, рд╣рдордиреЗ рдПрдХ рд╕реЗрд▓ рдмрдирд╛рдпрд╛ рд╣реИред
рдЕрдирд╛рд╡рд╢реНрдпрдХ рддрд░реАрдХреЗ
рдЪрд▓реЛ рдЗрд╕реЗ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдВред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ Form.update рдлрд╝рдВрдХреНрд╢рди рдХреЛ рдЬреЛрдбрд╝рдирд╛ рдЪрд╛рд╣рд┐рдП рдФрд░ рдЗрд╕реЗ рд╢реЗрдбреНрдпреВрд▓ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдПред
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)
рдкреНрд░рддреНрдпреЗрдХ рдЯрдЪ_рдбрд╛рдЙрди рдирд┐рд░реНрджреЗрд╢рд╛рдВрдХ = (рдЯрдЪ.x, рдЯрдЪ.рд╡рд╛рдИ) рдФрд░ 30 рдХреЗ рдЖрдХрд╛рд░ рдХреЗ рд╕рд╛рде рдПрдХ рд╕реЗрд▓ рдмрдирд╛рддрд╛ рд╣реИред рдлрд┐рд░, рд╣рдо рдЗрд╕реЗ рдкреНрд░рдкрддреНрд░ рдХреЗ рд╡рд┐рдЬреЗрдЯ рдХреЗ рд░реВрдк рдореЗрдВ рдФрд░ рд╣рдорд╛рд░реЗ рдЕрдкрдиреЗ рд╕рд░рдгреА рдореЗрдВ рдЬреЛрдбрд╝рддреЗ рд╣реИрдВ (рддрд╛рдХрд┐ рдЖрд╕рд╛рдиреА рд╕реЗ рдЙрдиреНрд╣реЗрдВ рдПрдХреНрд╕реЗрд╕ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП)ред
рдЕрдм рдЖрдк рдЕрдкрдиреЗ рдлреЙрд░реНрдо рдкрд░ рдЯреИрдк рдХрд░рдХреЗ рд╕реЗрд▓ рдЬрдирд░реЗрдЯ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред

рдиреАрдЯ рд╕реЗрдЯрд┐рдВрдЧреНрд╕
рдХреНрдпреЛрдВрдХрд┐ рд╣рдо рдПрдХ рдЕрдЪреНрдЫрд╛ рд╕рд╛рдБрдк рдкреНрд░рд╛рдкреНрдд рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ, рд╣рдореЗрдВ рдЧреНрд░рд╛рдлрд┐рдХрд▓ рдкреЛрдЬреАрд╢рди рдФрд░ рдХреЛрд╢рд┐рдХрд╛рдУрдВ рдХреА рд╡рд╛рд╕реНрддрд╡рд┐рдХ рд╕реНрдерд┐рддрд┐ рдореЗрдВ рдЕрдВрддрд░ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдПред
рдХреНрдпреЛрдВ?рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдмрд╣реБрдд рд╕рд╛рд░реЗ рдХрд╛рд░рдгред рд╕рднреА рддрд░реНрдХ рддрдерд╛рдХрдерд┐рдд рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдбреЗрдЯрд╛ рд╕реЗ рдЬреБрдбрд╝реЗ рд╣реЛрдиреЗ рдЪрд╛рд╣рд┐рдП, рдЬрдмрдХрд┐ рдЧреНрд░рд╛рдлрд┐рдХрд▓ рдбреЗрдЯрд╛ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдбреЗрдЯрд╛ рдХрд╛ рдкрд░рд┐рдгрд╛рдо рд╣реИред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЕрдЧрд░ рд╣рдо рдорд╛рд░реНрдЬрд┐рди рдмрдирд╛рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ, рддреЛ рд╕реЗрд▓ рдХрд╛ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдкреЙрдЬрд╝ (100, 100) рд╣реЛрдЧрд╛ рдЬрдмрдХрд┐ рдЖрдпрдд рдХрд╛ рдЧреНрд░рд╛рдлрд┐рдХрд▓ рдкреЙрдЬрд╝ - (102, 102)ред
рдЕрдЧрд░ рд╣рдо рд╢рд╛рд╕реНрддреНрд░реАрдп on_draw рд╕реЗ рдирд┐рдкрдЯрддреЗ рд╣реИрдВ рддреЛ PS рд╣рдо рдРрд╕рд╛ рдирд╣реАрдВ рдХрд░реЗрдВрдЧреЗред рд▓реЗрдХрд┐рди рдпрд╣рд╛рдБ рд╣рдореЗрдВ on_draw рдкрд░ рдкреНрд░реЛрдЧреНрд░рд╛рдо рдирд╣реАрдВ рдХрд░рдирд╛ рд╣реИред
рдЪрд▓реЛ рдХреГрдорд┐.рдХреЗрд╡реА рдлрд╝рд╛рдЗрд▓ рдХреЛ рдареАрдХ рдХрд░реЗрдВ:
<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 рдХреЗ рдмрдЬрд╛рдп X = 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)):
рдЖрдЗрдП рд╣рдорд╛рд░реА рдХреГрдорд┐ рдХреЛ рдЬреАрд╡рдирджрд╛рди рджреЗрдВред

рдкреНрд░рд╕реНрддрд╛рд╡
рдЕрдм рд╣рдо рдЗрд╕реЗ рдЖрдЧреЗ рдмрдврд╝рд╛рдПрдВрдЧреЗред
рдпрд╣ рд╕рд░рд▓ рд╣реИ:
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)

рдХреВрд▓ред
рдлрд▓ рдкреИрджрд╛ рдХрд░рдирд╛
рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рд╣рдо рдЗрд╕реЗ рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВред
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): ...
рдЗрдХрдЯреНрдард╛_рд╕реЗрдХреНрд╢рди рдХреЗ рдЕрдиреНрдп рд▓рд╛рднрд╡реИрд╕реЗ, рдЬрдм рд╣рдордиреЗ рдПрдХрддреНрд░рд┐рдд_ рдкреНрд░рд╕реНрддрд╛рд╡ рд▓рд╛рдЧреВ рдХрд┐рдП, рддрдм рд╣рдо 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)
рдФрд░ рдпрд╣ рдлреЙрд░реНрдо.рд╕реНрдЯрд╛рд░реНрдЯ рдХреЛ
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()

рдХреНрдпрд╛ рдЖрдк рдЕрднреА рднреА рдзреНрдпрд╛рди рджреЗ рд░рд╣реЗ рд╣реИрдВ? рдЕрдЧрд▓рд╛ рднрд╛рдЧ рд╕рдмрд╕реЗ рджрд┐рд▓рдЪрд╕реНрдк рд╣рд┐рд╕реНрд╕рд╛ рд╣реИред
рдмреЛрдирд╕ рдЕрдиреБрднрд╛рдЧ - рдЪрд┐рдХрдиреА рдЧрддрд┐
рдХреНрдпреЛрдВрдХрд┐ рдХреАрдбрд╝рд╛ рдХрд╛ рдЪрд░рдг рд╕реЗрд▓_рд╕рд╛рдЗрдЬ рдХреЗ рдмрд░рд╛рдмрд░ рд╣реИ, рдпрд╣ рдЙрддрдирд╛ рдЪрд┐рдХрдирд╛ рдирд╣реАрдВ рд╣реИред рд▓реЗрдХрд┐рди рд╣рдо рдЗрд╕реЗ рдЦреЗрд▓ рдХреЗ рдкреВрд░реЗ рддрд░реНрдХ рдХреЛ рдлрд┐рд░ рд╕реЗ рд▓рд┐рдЦреЗ рдмрд┐рдирд╛, рдЬрд┐рддрдирд╛ рд╕рдВрднрд╡ рд╣реЛ рдЙрддрдирд╛ рдмрд╛рд░ рд╕рдВрднрд╡ рдмрдирд╛рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВред рдЗрд╕рд▓рд┐рдП, рд╣рдореЗрдВ рдЕрдкрдиреЗ рдЧреНрд░рд╛рдлрд┐рдХрд▓ рдкреЛрдЬрд╝ рдХреЛ рдЖрдЧреЗ рдмрдврд╝рд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рддрдВрддреНрд░ рдмрдирд╛рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рд╣рдорд╛рд░реЗ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдкреЛрдЬрд╝ рдХреА рдирд╣реАрдВред рдЗрд╕рд▓рд┐рдП, рдореИрдВрдиреЗ рдПрдХ рд╕рд╛рдзрд╛рд░рдг рдлрд╛рдЗрд▓ рд▓рд┐рдЦреА рд╣реИ:
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 рдФрд░ copy-paste рдХрд░реЗрдВред
рдЕрдВрдд рдореЗрдВ, рдЪрд▓реЛ рдЗрд╕реЗ рдХрд╛рдо рдХрд░рддреЗ рд╣реИрдВ:
class Form(Widget): ... def __init__(self, config): ... self.smooth = smooth.XSmooth(["graphical_pos[0]", "graphical_pos[1]"])
рддрдм рд╣рдо self.worm.move () рдХреЛ рдкреНрд░рддрд┐рд╕реНрдерд╛рдкрд┐рдд рдХрд░рддреЗ рд╣реИрдВ
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 рджреНрд╡рд╛рд░рд╛ рд╕рдорд╛рдпреЛрдЬрд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛рдореБрдЭреЗ рдЕрдкрдиреЗ рдХреЛрдб рдХреЗ рд╕рд╛рде рдХреБрдЫ рдореБрджреНрджреЗ рдорд┐рд▓реЗ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЯреАрд╢рд░реНрдЯрдореИрди, рдХрд┐рд╡реА рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЗ рдпреЛрдЧрджрд╛рдирдХрд░реНрддрд╛рдУрдВ рдореЗрдВ рд╕реЗ рдПрдХ, рдиреЗ рдореБрдЭреЗ рд╕реЗрд▓ рдХреЛ рд╡рд┐рдЬреЗрдЯ рдХреЗ рд░реВрдк рдореЗрдВ рдирд╣реАрдВ рдмрдирд╛рдиреЗ рдХрд╛ рд╕реБрдЭрд╛рд╡ рджрд┐рдпрд╛, рд▓реЗрдХрд┐рди рдЗрд╕рдХреЗ рдмрдЬрд╛рдп рдПрдХ рдкреНрд╡рд╛рдЗрдВрдЯ рдирд┐рд░реНрджреЗрд╢ рдмрдирд╛рдпрд╛ред рд╣рд╛рд▓рд╛рдБрдХрд┐, рдореБрдЭреЗ рдпрд╣ рдХреЛрдб рд╕рдордЭрдиреЗ рдореЗрдВ рдЖрд╕рд╛рди рдирд╣реАрдВ рд╣реИ, рднрд▓реЗ рд╣реА рдпрд╣ рдпреВрдЖрдИ рдФрд░ рдЧреЗрдо рдбреЗрд╡рд▓рдкрдореЗрдВрдЯ рдХреЗ рдорд╛рдорд▓реЗ рдореЗрдВ рдирд┐рд╢реНрдЪрд┐рдд рд░реВрдк рд╕реЗ рдЕрдЪреНрдЫрд╛ рд╣реИред рд╡реИрд╕реЗ рднреА, рдХреЛрдб:
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
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
рдХреЛрдИ рднреА рдкреНрд░рд╢реНрди рдкреВрдЫрдиреЗ рдХреЗ рд▓рд┐рдП рд╕реНрд╡рддрдВрддреНрд░ рдорд╣рд╕реВрд╕ рдХрд░реЗрдВред