{"id":294269,"date":"2019-09-04T21:00:08","date_gmt":"2019-09-04T21:00:07","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=294269"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=294269","title":{"rendered":"\u041d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0437\u043c\u0435\u0439\u043a\u0438 \u0434\u043b\u044f Android \u043d\u0430 Kivy, Python"},"content":{"rendered":"\n<div class=\"post__text post__text-html js-mediator-article\">\u041f\u0440\u0438\u0432\u0435\u0442!<\/p>\n<p>  \u041c\u043d\u043e\u0433\u043e \u043b\u044e\u0434\u0435\u0439 \u0445\u043e\u0442\u0435\u043b\u0438 \u0431\u044b \u043d\u0430\u0447\u0430\u0442\u044c \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430 \u0430\u043d\u0434\u0440\u043e\u0438\u0434, \u043d\u043e Android Studio \u0438 Java \u0438\u0445 \u043e\u0442\u043f\u0443\u0433\u0438\u0432\u0430\u044e\u0442. \u041f\u043e\u0447\u0435\u043c\u0443? \u041f\u043e\u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u044d\u0442\u043e \u0432 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0441\u043c\u044b\u0441\u043b\u0435 \u0438\u0437 \u043f\u0443\u0448\u043a\u0438 \u043f\u043e \u0432\u043e\u0440\u043e\u0431\u044c\u044f\u043c. \u00ab\u042f \u043b\u0438\u0448\u044c \u0445\u043e\u0447\u0443 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0437\u043c\u0435\u0439\u043a\u0443, \u0438 \u0432\u0441\u0435!\u00bb<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/gb\/yu\/vz\/gbyuvzpeftcwxbxi9gwa2xz5axe.png\"><\/p>\n<p>  \u041d\u0430\u0447\u043d\u0435\u043c! (\u0431\u043e\u043d\u0443\u0441 \u0432 \u043a\u043e\u043d\u0446\u0435)<br \/>  <a name=\"habracut\"><\/a>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u0417\u0430\u0447\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b \u043f\u043e \u0437\u043c\u0435\u0439\u043a\u0435 \u043d\u0430 Kivy? (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0434\u043b\u044f \u043f\u0440\u043e\u0447\u0442\u0435\u043d\u0438\u044f)<\/b><\/p>\n<div class=\"spoiler_text\">\u0415\u0441\u043b\u0438 \u0432\u044b \u2014 \u043f\u0438\u0442\u043e\u043d\u0438\u0441\u0442, \u0438 \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0447\u0430\u0442\u044c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0443 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0438\u0433\u0440 \u043f\u043e\u0434 \u0430\u043d\u0434\u0440\u043e\u0438\u0434, \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0443\u0436\u0435 \u0437\u0430\u0433\u0443\u0433\u043b\u0438\u043b\u0438 \u00ab\u0437\u043c\u0435\u0439\u043a\u0430 \u043d\u0430 \u0430\u043d\u0434\u0440\u043e\u0438\u0434\u0435\u00bb \u0438 \u043d\u0430\u0448\u043b\u0438 <a href=\"https:\/\/www.digitaljunky.io\/make-a-snake-game-for-android-written-in-python-part-2\/\">\u044d\u0442\u043e (Eng)<\/a> \u0438\u043b\u0438 \u0435\u0435 <a href=\"https:\/\/tproger.ru\/translations\/snake-android-python-kivy\/\">\u043f\u0435\u0440\u0435\u0432\u043e\u0434 (\u0420\u0443\u0441)<\/a>. \u0418 \u044f \u0442\u043e\u0436\u0435 \u0442\u0430\u043a \u0441\u0434\u0435\u043b\u0430\u043b. \u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u044f \u043d\u0430\u0448\u0435\u043b \u0441\u0442\u0430\u0442\u044c\u044e \u0431\u0435\u0441\u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0439 \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c:<\/p>\n<h5>\u041f\u043b\u043e\u0445\u043e\u0439 \u043a\u043e\u0434<\/h5>\n<p>  \u041c\u0435\u043b\u043a\u0438\u0435 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u0438:<\/p>\n<ol>\n<li>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u00ab\u0445\u0432\u043e\u0441\u0442\u0430\u00bb \u0438 \u00ab\u0433\u043e\u043b\u043e\u0432\u044b\u00bb \u043f\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438. \u0412 \u044d\u0442\u043e\u043c \u043d\u0435\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438, \u0442\u0430\u043a \u043a\u0430\u043a \u0432 \u0437\u043c\u0435\u0435 \u0433\u043e\u043b\u043e\u0432\u0430 \u2014 \u043f\u0435\u0440\u0432\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0445\u0432\u043e\u0441\u0442\u0430. \u041d\u0435 \u0441\u0442\u043e\u0438\u0442 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u0441\u044e \u0437\u043c\u0435\u044e \u0434\u0435\u043b\u0438\u0442\u044c \u043d\u0430 \u0434\u0432\u0435 \u0447\u0430\u0441\u0442\u0438, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043a\u043e\u0434 \u043f\u0438\u0448\u0435\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e.<\/li>\n<li>Clock.schedule \u043e\u0442 self.update \u0432\u044b\u0437\u0432\u0430\u043d \u0438\u0437\u2026 self.update.<\/li>\n<li>\u041a\u043b\u0430\u0441\u0441 \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0443\u0440\u043e\u0432\u043d\u044f (\u0443\u0441\u043b\u043e\u0432\u043d\u043e \u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u0438\u0437 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430) Playground \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d \u0432 \u043d\u0430\u0447\u0430\u043b\u0435, \u043d\u043e \u043a\u043b\u0430\u0441\u0441 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0443\u0440\u043e\u0432\u043d\u044f SnakeApp \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d \u0432 \u043a\u043e\u043d\u0446\u0435 \u0444\u0430\u0439\u043b\u0430.<\/li>\n<li>\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0434\u043b\u044f \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0439 (\u00abup\u00bb, \u00abdown\u00bb, &#8230;) \u0432\u043c\u0435\u0441\u0442\u043e \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432 ( (0, 1), (1, 0)\u2026 ).<\/li>\n<\/ol>\n<p>  \u0421\u0435\u0440\u044c\u0435\u0437\u043d\u044b\u0435 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u0438:  <\/p>\n<ol>\n<li>\u0414\u0438\u043d\u0430\u043c\u0438\u0447\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b (\u043a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u0444\u0440\u0443\u043a\u0442) \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u0435\u043d\u044b \u043a \u0444\u0430\u0439\u043b\u0443 kv, \u0442\u0430\u043a \u0447\u0442\u043e \u0432\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u043e\u0434\u043d\u043e\u0433\u043e \u044f\u0431\u043b\u043e\u043a\u0430 \u043d\u0435 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u0430\u0432 \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0443 \u043a\u043e\u0434\u0430<\/li>\n<li>\u0427\u0443\u0434\u043d\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u0437\u043c\u0435\u0438 \u0432\u043c\u0435\u0441\u0442\u043e \u043a\u043b\u0435\u0442\u043a\u0430-\u0437\u0430-\u043a\u043b\u0435\u0442\u043a\u043e\u0439.<\/li>\n<li>350 \u0441\u0442\u0440\u043e\u043a \u2014 \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0434\u043b\u0438\u043d\u043d\u044b\u0439 \u043a\u043e\u0434.<\/li>\n<\/ol>\n<p>  <\/p>\n<h5>\u0421\u0442\u0430\u0442\u044c\u044f \u043d\u0435\u043e\u0447\u0435\u0432\u0438\u0434\u043d\u0430 \u0434\u043b\u044f \u043d\u043e\u0432\u0438\u0447\u043a\u043e\u0432<\/h5>\n<p>  \u042d\u0442\u043e \u043c\u043e\u0435 \u041b\u0418\u0427\u041d\u041e\u0415 \u043c\u043d\u0435\u043d\u0438\u0435. \u0411\u043e\u043b\u0435\u0435 \u0442\u043e\u0433\u043e, \u044f \u043d\u0435 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u0443\u044e, \u0447\u0442\u043e \u043c\u043e\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u0431\u0443\u0434\u0435\u0442 \u0431\u043e\u043b\u0435\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0439 \u0438 \u043f\u043e\u043d\u044f\u0442\u043d\u043e\u0439. \u041d\u043e \u043f\u043e\u0441\u0442\u0430\u0440\u0430\u044e\u0441\u044c, \u0430 \u0435\u0449\u0435 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u0443\u044e:<\/p>\n<ol>\n<li>\u041a\u043e\u0434 \u0431\u0443\u0434\u0435\u0442 \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u043c<\/li>\n<li>\u0417\u043c\u0435\u0439\u043a\u0430 \u043a\u0440\u0430\u0441\u0438\u0432\u043e\u0439 (\u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e)<\/li>\n<li>\u0422\u0443\u0442\u043e\u0440\u0438\u0430\u043b \u0431\u0443\u0434\u0435\u0442 \u0438\u043c\u0435\u0442\u044c \u043f\u043e\u044d\u0442\u0430\u043f\u043d\u043e\u0435 \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u0435<\/li>\n<\/ol>\n<p>  <\/p>\n<h5>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043d\u0435 \u043a\u043e\u043c\u0438\u043b\u044c\u0444\u043e<\/h5>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/n6\/q-\/li\/n6q-lieen74-rcs-tzwrr1yguwa.png\"><br \/>  \u041d\u0435\u0442 \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043c\u0435\u0436\u0434\u0443 \u043a\u043b\u0435\u0442\u043a\u0430\u043c\u0438, \u0447\u0443\u0434\u043d\u043e\u0439 \u0442\u0440\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a, \u0434\u0435\u0440\u0433\u0430\u044e\u0449\u0430\u044f\u0441\u044f \u0437\u043c\u0435\u0439\u043a\u0430.  <\/div>\n<\/div>\n<p>  <\/p>\n<h1>\u0417\u043d\u0430\u043a\u043e\u043c\u0441\u0442\u0432\u043e<\/h1>\n<p>  <\/p>\n<h3>\u041f\u0435\u0440\u0432\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435<\/h3>\n<p>  \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u0442\u0435\u0441\u044c \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u0443\u0436\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0438 Kivy (\u0435\u0441\u043b\u0438 \u043d\u0435\u0442, \u0441\u043b\u0435\u0434\u0443\u0439\u0442\u0435 <a href=\"https:\/\/buildmedia.readthedocs.org\/media\/pdf\/kivy\/master\/kivy.pdf\">\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c<\/a>) \u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435<br \/>  <code>buildozer init<\/code> \u0432 \u043f\u0430\u043f\u043a\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430. <\/p>\n<p>  \u0417\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u043f\u0435\u0440\u0432\u0443\u044e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0443:<\/p>\n<p>  main.py<\/p>\n<pre><code class=\"python\">from kivy.app import App from kivy.uix.widget import Widget  class WormApp(App):     def build(self):         return Widget()  if __name__ == '__main__':     WormApp().run() <\/code><\/pre>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/ms\/he\/ud\/msheudwu1tc3wcvjj8z2x9oswgi.png\"><\/p>\n<p>  \u041c\u044b \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u0432\u0438\u0434\u0436\u0435\u0442. \u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443 \u0438\u043b\u0438 \u043b\u044e\u0431\u043e\u0439 \u0434\u0440\u0443\u0433\u043e\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430:<\/p>\n<pre><code class=\"python\">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() <\/code><\/pre>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/gp\/k1\/cn\/gpk1cngh7ns5z9wdtjuua5d1ogg.png\"><\/p>\n<p>  \u0423\u0440\u0430! \u041f\u043e\u0437\u0434\u0440\u0430\u0432\u043b\u044f\u044e! \u0412\u044b \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u043a\u043d\u043e\u043f\u043a\u0443!<\/p>\n<h3>\u0424\u0430\u0439\u043b\u044b .kv<\/h3>\n<p>  \u041e\u0434\u043d\u0430\u043a\u043e, \u0435\u0441\u0442\u044c \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0442\u0430\u043a\u0438\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u0431\u044a\u044f\u0432\u0438\u043c \u043d\u0430\u0448\u0443 \u0444\u043e\u0440\u043c\u0443:<\/p>\n<pre><code class=\"python\">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() <\/code><\/pre>\n<p>  \u0417\u0430\u0442\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u00abworm.kv\u00bb \u0444\u0430\u0439\u043b.<\/p>\n<p>  worm.kv<\/p>\n<pre><code class=\"plaintext\">&lt;Form&gt;:     but2: but_id      Button:         id: but_id         pos: (200, 200) <\/code><\/pre>\n<p>  \u0427\u0442\u043e \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u043e? \u041c\u044b \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u0435\u0449\u0435 \u043e\u0434\u043d\u0443 \u043a\u043d\u043e\u043f\u043a\u0443 \u0438 \u043f\u0440\u0438\u0441\u0432\u043e\u0438\u043c id but_id. \u0422\u0435\u043f\u0435\u0440\u044c but_id \u0430\u0441\u0441\u043e\u0446\u0438\u0438\u0440\u043e\u0432\u0430\u043d\u043e \u0441 but2 \u0444\u043e\u0440\u043c\u044b. \u042d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u043c\u044b button \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e but2:<\/p>\n<pre><code class=\"python\">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\" <\/code><\/pre>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/ug\/a6\/88\/uga688dhltpl2klklqbigyodu40.png\"><\/p>\n<h3>\u0413\u0440\u0430\u0444\u0438\u043a\u0430<\/h3>\n<p>  \u0414\u0430\u043b\u0435\u0435 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u0431\u044a\u044f\u0432\u0438\u043c \u0435\u0433\u043e \u0432 worm.kv:<\/p>\n<pre><code class=\"plaintext\">&lt;Form&gt;:  &lt;Cell&gt;:     canvas:         Rectangle:             size: self.size             pos: self.pos <\/code><\/pre>\n<p>  \u041c\u044b \u0441\u0432\u044f\u0437\u0430\u043b\u0438 \u043f\u043e\u0437\u0438\u0446\u0438\u044e \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430 \u0441 self.pos \u0438 \u0435\u0433\u043e \u0440\u0430\u0437\u043c\u0435\u0440 \u0441 self.size. \u0422\u0430\u043a \u0447\u0442\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u044d\u0442\u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b \u0438\u0437 Cell, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u043b\u0435\u0442\u043a\u0443, \u043c\u044b \u043c\u0435\u043d\u044f\u0442\u044c \u0435\u0435 \u0440\u0430\u0437\u043c\u0435\u0440 \u0438 \u043f\u043e\u0437\u0438\u0446\u0438\u044e:<\/p>\n<pre><code class=\"python\">class Cell(Widget):     def __init__(self, x, y, size):         super().__init__()         self.size = (size, size)   # \u041a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c self.size \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0435\u0441\u0442\u044c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e \"size\" \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430         self.pos = (x, y)  class Form(Widget):     def __init__(self):         super().__init__()         self.cell = Cell(100, 100, 30)         self.add_widget(self.cell) <\/code><\/pre>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/we\/j4\/dn\/wej4dnu0jojperjlmnlflu5dxi8.png\"><\/p>\n<p>  \u041e\u043a\u0435\u0439, \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u043a\u043b\u0435\u0442\u043a\u0443.<\/p>\n<h3>\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b<\/h3>\n<p>  \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0434\u0432\u0438\u0433\u0430\u0442\u044c \u0437\u043c\u0435\u044e. \u0427\u0442\u043e\u0431\u044b \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e Form.update \u0438 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u043a \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u044e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Clock.schedule.<\/p>\n<pre><code class=\"python\">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()  <\/code><\/pre>\n<p>  \u041a\u043b\u0435\u0442\u043a\u0430 \u0431\u0443\u0434\u0435\u0442 \u0434\u0432\u0438\u0433\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u0444\u043e\u0440\u043c\u0435. \u041a\u0430\u043a \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0438\u0434\u0435\u0442\u044c, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u0439\u043c\u0435\u0440 \u043d\u0430 \u043b\u044e\u0431\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Clock.<\/p>\n<p>  \u0414\u0430\u043b\u0435\u0435, \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043d\u0430\u0436\u0430\u0442\u0438\u044f (touch event). \u041f\u0435\u0440\u0435\u043f\u0438\u0448\u0435\u043c Form:<\/p>\n<pre><code class=\"python\">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) <\/code><\/pre>\n<p>  \u041a\u0430\u0436\u0434\u044b\u0439 touch_down \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043a\u043b\u0435\u0442\u043a\u0443 \u0441 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430\u043c\u0438 = (touch.x, touch.y) \u0438 \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u043c = 30. \u0417\u0430\u0442\u0435\u043c, \u043c\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0435\u0435 \u043a\u0430\u043a \u0432\u0438\u0434\u0436\u0435\u0442 \u0444\u043e\u0440\u043c\u044b \u0418 \u0432 \u043d\u0430\u0448 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u043c\u0430\u0441\u0441\u0438\u0432 (\u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0437\u0434\u043d\u0435\u0435 \u043e\u0431\u0440\u0430\u0449\u0430\u0442\u044c\u0441\u044f \u043a \u043d\u0435\u043c\u0443).<\/p>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u043a\u0430\u0436\u0434\u043e\u0435 \u043d\u0430\u0436\u0430\u0442\u0438\u0435 \u043d\u0430 \u0444\u043e\u0440\u043c\u0443 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u043a\u043b\u0435\u0442\u043a\u0443.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/sf\/rt\/of\/sfrtofg5ylwk8pxm3mowelxw1by.png\"><\/p>\n<h3>\u041d\u044f\u0448\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438<\/h3>\n<p>  \u0422\u0430\u043a \u043a\u0430\u043a \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043a\u0440\u0430\u0441\u0438\u0432\u0443\u044e \u0437\u043c\u0435\u0439\u043a\u0443, \u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0438 \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0438 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0443\u044e \u043f\u043e\u0437\u0438\u0446\u0438\u0438. <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u0417\u0430\u0447\u0435\u043c?<\/b><\/p>\n<div class=\"spoiler_text\">\u041c\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u0447\u0438\u043d \u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e. \u0412\u0441\u044f \u043b\u043e\u0433\u0438\u043a\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0430 \u0441 \u0442\u0430\u043a \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u043c\u043e\u0439 \u043d\u0430\u0441\u0442\u043e\u044f\u0437\u0435\u0439 \u043f\u043e\u0437\u0438\u0446\u0438\u0435\u0439, \u0430 \u0432\u043e\u0442 \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u2014 \u0435\u0441\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u0439. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0435\u0441\u043b\u0438 \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043e\u0442\u0441\u0442\u0443\u043f\u044b, \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0430\u044f \u043f\u043e\u0437\u0438\u0446\u0438\u044f \u0431\u0443\u0434\u0435\u0442 (100, 100) \u043f\u043e\u043a\u0430 \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u2014 (102, 102).<\/p>\n<p>  P. S. \u041c\u044b \u0431\u044b \u044d\u0442\u0438\u043c \u043d\u0435 \u043f\u0430\u0440\u0438\u043b\u0438\u0441\u044c \u0435\u0441\u043b\u0438 \u0431\u044b \u0438\u043c\u0435\u043b\u0438 \u0434\u0435\u043b\u043e \u0441 on_draw. \u041d\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u043d\u0435 \u043e\u0431\u044f\u0437\u0430\u043d\u044b \u043f\u0435\u0440\u0435\u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u0444\u043e\u0440\u043c\u0443 \u043b\u0430\u043f\u043a\u0430\u043c\u0438.  <\/div>\n<\/div>\n<p>  \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u043c \u0444\u0430\u0439\u043b worm.kv:<\/p>\n<pre><code class=\"plaintext\">&lt;Form&gt;:  &lt;Cell&gt;:     canvas:         Rectangle:             size: self.graphical_size             pos: self.graphical_pos <\/code><\/pre>\n<p>  \u0438 main.py:<\/p>\n<pre><code class=\"python\">... 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) ... <\/code><\/pre>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/sa\/0m\/yi\/sa0myibds8nzaymucynrard6ixk.png\"><\/p>\n<p>  \u041f\u043e\u044f\u0432\u0438\u043b\u0441\u044f \u043e\u0442\u0441\u0442\u0443\u043f, \u0442\u0430\u043a \u0447\u0442\u043e \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043b\u0443\u0447\u0448\u0435 \u043d\u0435 \u0441\u043c\u043e\u0442\u0440\u044f \u043d\u0430 \u0442\u043e, \u0447\u0442\u043e \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u0432\u0442\u043e\u0440\u0443\u044e \u043a\u043b\u0435\u0442\u043a\u0443 \u0441 X = 130 \u0432\u043c\u0435\u0441\u0442\u043e 132. \u041f\u043e\u0437\u0436\u0435 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0434\u0435\u043b\u0430\u0442\u044c \u043c\u044f\u0433\u043a\u043e\u0435 \u043f\u0435\u0440\u0435\u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0435, \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u043d\u0430 \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u043c\u0435\u0436\u0434\u0443 actual_pos \u0438 graphical_pos.<\/p>\n<h1>\u041f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0447\u0435\u0440\u0432\u044f\u043a\u0430<\/h1>\n<p>  <\/p>\n<h3>\u041e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0438\u0435<\/h3>\n<p>  \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c config \u0432 main.py<\/p>\n<pre><code class=\"python\">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) <\/code><\/pre>\n<p>  (\u041f\u043e\u0432\u0435\u0440\u044c\u0442\u0435, \u0432\u044b \u044d\u0442\u043e \u043f\u043e\u043b\u044e\u0431\u0438\u0442\u0435!)<\/p>\n<p>  \u0417\u0430\u0442\u0435\u043c \u043f\u0440\u0438\u0441\u0432\u043e\u0439\u0442\u0435 config \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e:<\/p>\n<pre><code class=\"python\">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 <\/code><\/pre>\n<p>  \u041f\u0435\u0440\u0435\u043f\u0438\u0448\u0438\u0442\u0435 init \u0438 start:<\/p>\n<pre><code class=\"python\">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) <\/code><\/pre>\n<p>  \u0417\u0430\u0442\u0435\u043c, Cell:<\/p>\n<pre><code class=\"python\">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) <\/code><\/pre>\n<p>  \u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u044d\u0442\u043e \u0431\u044b\u043b\u043e \u0431\u043e\u043b\u0435\u0435 \u043c\u0435\u043d\u0435\u0435 \u043f\u043e\u043d\u044f\u0442\u043d\u043e.<\/p>\n<p>  \u0418 \u043d\u0430\u043a\u043e\u043d\u0435\u0446 Worm:<\/p>\n<pre><code class=\"python\">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)):         # \u0415\u0441\u043b\u0438 \u043f\u043e\u0437\u0438\u0446\u0438\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0430, \u043c\u044b \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u0435\u043c \u043a\u043b\u0435\u0442\u043a\u0443 \u0442\u0443\u0434\u0430, \u0438\u043d\u0430\u0447\u0435 - \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 \u0434\u0430\u043d\u043d\u044b\u043c \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043c         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) <\/code><\/pre>\n<p>  \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u0430\u0448\u0435\u0433\u043e \u0447\u0435\u0440\u0432\u044f\u0447\u043a\u0430.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/x8\/89\/ve\/x889vemqslo2x8rxvp54jgbwgpi.png\"><\/p>\n<h3>\u0414\u0432\u0438\u0436\u0435\u043d\u0438\u0435<\/h3>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u0434\u0432\u0438\u0433\u0430\u0435\u043c \u042d\u0422\u041e.<\/p>\n<p>  \u0422\u0443\u0442 \u043f\u0440\u043e\u0441\u0442\u043e:<\/p>\n<pre><code class=\"python\">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) <\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"python\">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) <\/code><\/pre>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/jx\/8v\/jw\/jx8vjwv5zpnnhfprs7peo1o_gfm.png\"><\/p>\n<p>  \u041e\u043d\u043e \u0436\u0438\u0432\u043e\u0435! \u041e\u043d\u043e \u0436\u0438\u0432\u043e\u0435!<\/p>\n<h3>\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435<\/h3>\n<p>  \u041a\u0430\u043a \u0432\u044b \u043c\u043e\u0433\u043b\u0438 \u0441\u0443\u0434\u0438\u0442\u044c \u043f\u043e \u043f\u0435\u0440\u0432\u043e\u0439 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0435, \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u043c\u0435\u0438 \u0431\u0443\u0434\u0435\u0442 \u0442\u0430\u043a\u0438\u043c:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/va\/u0\/ad\/vau0adepnd4fsjgcjxe0lp6jkqk.png\"><\/p>\n<pre><code class=\"python\">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 &gt; hs and aws &gt; hs:             cur_dir = (0, -1)         # \u0412\u043d\u0438\u0437         elif ws &gt; hs &gt;= aws:             cur_dir = (1, 0)          # \u0412\u043f\u0440\u0430\u0432\u043e         elif ws &lt;= hs &lt; aws:             cur_dir = (-1, 0)         # \u0412\u043b\u0435\u0432\u043e         else:             cur_dir = (0, 1)           # \u0412\u0432\u0435\u0440\u0445         self.cur_dir = cur_dir <\/code><\/pre>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/ff\/8k\/c2\/ff8kc2ur1rct6cjmkt-ozc9xch4.png\"><\/p>\n<p>  \u0417\u0434\u043e\u0440\u043e\u0432\u043e.<\/p>\n<h3>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0444\u0440\u0443\u043a\u0442\u0430<\/h3>\n<p>  \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u0431\u044a\u044f\u0432\u0438\u043c.<\/p>\n<pre><code class=\"python\">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) <\/code><\/pre>\n<p>  \u0422\u0435\u043a\u0443\u0449\u0438\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/bb\/t0\/wh\/bbt0whhowgmfdhnyjawtmnbv0ny.png\"><\/p>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043e\u0431\u044a\u044f\u0432\u0438\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0435\u0442\u043e\u0434\u043e\u0432 Worm:<\/p>\n<pre><code class=\"python\">class Worm(Widget): ...     # \u0422\u0443\u0442 \u0441\u043e\u0431\u0435\u0440\u0435\u043c \u043f\u043e\u0437\u0438\u0446\u0438\u0438 \u0432\u0441\u0435\u0445 \u043a\u043b\u0435\u0442\u043e\u043a     def gather_positions(self):         return [cell.get_pos() for cell in self.cells]     # \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043f\u0435\u0440\u0435\u0441\u0435\u043a\u0430\u0435\u0442\u0441\u044f \u043b\u0438 \u0433\u043e\u043b\u043e\u0432\u0430 \u0441 \u0434\u0440\u0443\u0433\u0438\u043c \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u043c     def head_intersect(self, cell):         return self.cells[0].get_pos() == cell.get_pos() <\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u0414\u0440\u0443\u0433\u0438\u0435 \u0431\u043e\u043d\u0443\u0441\u044b \u0444\u0443\u043d\u043a\u0446\u0438\u0438 gather_positions<\/b><\/p>\n<div class=\"spoiler_text\">\u041a\u0441\u0442\u0430\u0442\u0438, \u043f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e, \u043a\u0430\u043a \u043c\u044b \u043e\u0431\u044a\u044f\u0432\u0438\u043b\u0438 gather_positions, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0443\u043b\u0443\u0447\u0448\u0438\u0442\u044c fruit_dislocate:<\/p>\n<pre><code class=\"python\">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) <\/code><\/pre>\n<p>  \u041d\u0430 \u044d\u0442\u043e\u0442 \u043c\u043e\u043c\u0435\u043d\u0442\u0435 \u043f\u043e\u0437\u0438\u0446\u0438\u044f \u044f\u0431\u043b\u043e\u043a\u0430 \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c \u0441 \u043f\u043e\u0437\u0438\u0446\u0438\u0438 \u0445\u0432\u043e\u0441\u0442\u0430  <\/div>\n<\/div>\n<p>  \u2026 \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0432 update()<\/p>\n<pre><code class=\"python\">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() <\/code><\/pre>\n<p>  <\/p>\n<h3>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u044f \u0433\u043e\u043b\u043e\u0432\u044b \u0438 \u0445\u0432\u043e\u0441\u0442\u0430<\/h3>\n<p>  \u041c\u044b \u0445\u043e\u0442\u0438\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u0442\u0430 \u0436\u0435 \u043b\u0438 \u043f\u043e\u0437\u0438\u0446\u0438\u044f \u0443 \u0433\u043e\u043b\u043e\u0432\u044b, \u0447\u0442\u043e \u0443 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043a\u043b\u0435\u0442\u043a\u0438 \u0445\u0432\u043e\u0441\u0442\u0430.<\/p>\n<pre><code class=\"python\">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 <\/code><\/pre>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/fu\/yt\/m4\/fuytm4is8yrdbrlym0c5yj8bta8.png\"><\/p>\n<h3>\u0420\u0430\u0441\u043a\u0440\u0430\u0448\u0438\u0432\u0430\u043d\u0438\u0435, \u0434\u0435\u043a\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u043d\u0433 \u043a\u043e\u0434\u0430<\/h3>\n<p>  \u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u043d\u0433\u0430.<\/p>\n<p>  \u041f\u0435\u0440\u0435\u043f\u0438\u0448\u0435\u043c \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043c<\/p>\n<pre><code class=\"python\">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         ... <\/code><\/pre>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0435\u0441\u043b\u0438 \u0447\u0435\u0440\u0432\u044f\u043a \u043c\u0435\u0440\u0442\u0432 (\u0437\u0430\u043c\u043e\u0440\u043e\u0436\u0435\u043d), \u0435\u0441\u043b\u0438 \u0432\u044b \u043d\u0430\u0436\u043c\u0435\u0442\u0435 \u043d\u0430 \u044d\u043a\u0440\u0430\u043d, \u0438\u0433\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0447\u0430\u0442\u0430 \u0437\u0430\u043d\u043e\u0432\u043e.<\/p>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u043c \u043a \u0434\u0435\u043a\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044e \u0438 \u0440\u0430\u0441\u043a\u0440\u0430\u0448\u0438\u0432\u0430\u043d\u0438\u044e.<\/p>\n<p>  worm.kv<\/p>\n<pre><code class=\"plaintext\">&lt;Form&gt;:     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   &lt;Worm&gt;:   &lt;Cell&gt;:     canvas:         Color:             rgba: self.color         Rectangle:             size: self.graphical_size             pos: self.graphical_pos <\/code><\/pre>\n<p>  \u041f\u0435\u0440\u0435\u043f\u0438\u0448\u0435\u043c WormApp:<\/p>\n<pre><code class=\"python\">class WormApp(App):     def build(self):         self.config = Config()         self.form = Form(self.config)         return self.form      def on_start(self):         self.form.start() <\/code><\/pre>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/fk\/cq\/6v\/fkcq6vobxfathjjvs3pg3aak7oo.png\"><\/p>\n<p>  \u0420\u0430\u0441\u043a\u0440\u0430\u0441\u0438\u043c. \u041f\u0435\u0440\u0435\u043f\u0438\u0448\u0435\u043c Cell in .kv:<\/p>\n<pre><code class=\"plaintext\">&lt;Cell&gt;:     canvas:         Color:             rgba: self.color          Rectangle:             size: self.graphical_size             pos: self.graphical_pos <\/code><\/pre>\n<p>  \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u044d\u0442\u043e \u043a Cell.__init__:  <\/p>\n<pre><code class=\"python\">self.color = (0.2, 1.0, 0.2, 1.0)    #  <\/code><\/pre>\n<p>  \u0438 \u044d\u0442\u043e \u043a Form.start<\/p>\n<pre><code class=\"python\">self.fruit.color = (1.0, 0.2, 0.2, 1.0) <\/code><\/pre>\n<p>  \u041f\u0440\u0435\u0432\u043e\u0441\u0445\u043e\u0434\u043d\u043e, \u043d\u0430\u0441\u043b\u0430\u0436\u0434\u0430\u0439\u0442\u0435\u0441\u044c \u0437\u043c\u0435\u0439\u043a\u043e\u0439<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/1e\/nh\/6f\/1enh6fainu28aqcei-9fg0of8iq.png\"><\/p>\n<p>  \u041d\u0430\u043a\u043e\u043d\u0435\u0446, \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u0430\u0434\u043f\u0438\u0441\u044c \u00abgame over\u00bb<\/p>\n<pre><code class=\"python\">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\") <\/code><\/pre>\n<p>  \u0418 \u0437\u0430\u0434\u0430\u0434\u0438\u043c \u00ab\u0440\u0430\u043d\u0435\u043d\u043e\u0439\u00bb \u043a\u043b\u0435\u0442\u043a\u0435 \u043a\u0440\u0430\u0441\u043d\u044b\u0439 \u0446\u0432\u0435\u0442:<\/p>\n<p>  \u0432\u043c\u0435\u0441\u0442\u043e<\/p>\n<pre><code class=\"python\">    def update(self, _):     ...         if self.worm_bite_self():             self.game_over()     ... <\/code><\/pre>\n<p>  \u043d\u0430\u043f\u0438\u0448\u0438\u0442\u0435<\/p>\n<pre><code class=\"python\">    def update(self, _):         cell = self.worm_bite_self()         if cell:             cell.color = (1.0, 0.2, 0.2, 1.0)             self.game_over() <\/code><\/pre>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/lw\/mr\/na\/lwmrnagdbvjt79qacs0kt9jg_2k.png\"><\/p>\n<p>  \u0412\u044b \u0435\u0449\u0435 \u0442\u0443\u0442? \u0421\u0430\u043c\u0430\u044f \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0432\u043f\u0435\u0440\u0435\u0434\u0438!<\/p>\n<h3>\u0411\u043e\u043d\u0443\u0441 \u2014 \u043f\u043b\u0430\u0432\u043d\u043e\u0435 \u0436\u0432\u0438\u0436\u0435\u043d\u0438\u0435<\/h3>\n<p>  \u0422\u0430\u043a \u043a\u0430\u043a \u0448\u0430\u0433 \u0447\u0435\u0440\u0432\u044f\u0447\u043a\u0430 \u0440\u0430\u0432\u0435\u043d cell_size, \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u043f\u043b\u0430\u0432\u043d\u043e. \u041d\u043e \u043c\u044b \u0431\u044b \u0445\u043e\u0442\u0435\u043b\u0438 \u0448\u0430\u0433\u0430\u0442\u044c \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0447\u0430\u0449\u0435 \u0431\u0435\u0437 \u043f\u043e\u043b\u043d\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u044b\u0432\u0430\u043d\u0438\u044f \u043b\u043e\u0433\u0438\u043a\u0438 \u0438\u0433\u0440\u044b. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043d\u0430\u043c \u043d\u0443\u0436\u0435\u043d \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u0432\u0438\u0433\u0430\u043b \u0431\u044b \u043d\u0430\u0448\u0438 \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u043e\u0437\u0438\u0446\u0438\u0438 (graphical_pos) \u043d\u043e \u043d\u0435 \u0432\u043b\u0438\u044f\u043b \u0431\u044b \u043d\u0430 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0435 (actual_pos). \u042f \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043a\u043e\u0434:<\/p>\n<p>  smooth.py<\/p>\n<pre><code class=\"python\">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 &gt;= 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)  <\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u0422\u0435\u043c, \u043a\u043e\u043c\u0443 \u043d\u0435 \u043f\u043e\u043d\u0440\u0430\u0432\u0438\u043b\u0441\u044f \u0441\u0435\u0439 \u043a\u043e\u0434<\/b><\/p>\n<div class=\"spoiler_text\">\u042d\u0442\u043e\u0442 \u043c\u043e\u0434\u0443\u043b\u044c \u043d\u0435 \u0435\u0441\u0442\u044c \u0432\u0435\u0440\u0445 \u044d\u043b\u0435\u0433\u0430\u043d\u0442\u043d\u043e\u0441\u0442\u0438. \u042f \u043f\u0440\u0438\u0437\u043d\u0430\u044e \u044d\u0442\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u043f\u043b\u043e\u0445\u0438\u043c. \u041d\u043e \u044d\u0442\u043e \u0442\u043e\u043b\u044c\u043a\u043e hello-world \u0440\u0435\u0448\u0435\u043d\u0438\u0435.  <\/div>\n<\/div>\n<p>  \u0422\u0430\u043a, \u0432\u044b \u043b\u0438\u0448\u044c \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0435 smooth.py and \u0438 \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442\u0435 \u043a\u043e\u0434 \u0432 \u0444\u0430\u0439\u043b.<br \/>  \u041d\u0430\u043a\u043e\u043d\u0435\u0446, \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u043c \u042d\u0422\u041e \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c!<\/p>\n<pre><code class=\"python\">class Form(Widget): ...     def __init__(self, config):     ...         self.smooth = smooth.XSmooth([\"graphical_pos[0]\", \"graphical_pos[1]\"]) <\/code><\/pre>\n<p>  \u0417\u0430\u043c\u0435\u043d\u0438\u043c self.worm.move() \u0441<\/p>\n<pre><code class=\"python\">class Form(Widget): ...     def update(self, _):     ...         self.worm.move(self.cur_dir, smooth_motion=(self.smooth, self.config.INTERVAL)) <\/code><\/pre>\n<p>  \u0410 \u044d\u0442\u043e \u043a\u0430\u043a \u043c\u0435\u0442\u043e\u0434\u044b Cell \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442\u044c<\/p>\n<pre><code class=\"python\">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) <\/code><\/pre>\n<p>  \u041d\u0443 \u0432\u043e\u0442 \u0438 \u0432\u0441\u0435, \u0441\u043f\u0430\u0441\u0438\u0431\u043e \u0437\u0430 \u0432\u0430\u0448\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435! \u041a\u043e\u0434 \u0441\u043d\u0438\u0437\u0443.<\/p>\n<p>  \u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0435 \u0432\u0438\u0434\u0435\u043e \u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<\/p>\n<div class=\"oembed\">\n<div>\n<div style=\"left: 0; width: 100%; height: 0; position: relative; padding-bottom: 56.2493%;\"><iframe src=\"https:\/\/www.youtube.com\/embed\/0clOAucqRDE?rel=0&amp;showinfo=1&amp;hl=en-US\" style=\"border: 0; top: 0; left: 0; width: 100%; height: 100%; position: absolute;\" allowfullscreen scrolling=\"no\"><\/iframe><\/div>\n<\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u0424\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434<\/b><\/p>\n<div class=\"spoiler_text\">\n<div class=\"spoiler\"><b class=\"spoiler_title\">main.py<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"python\">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 &gt; hs and aws &gt; hs:             cur_dir = (0, -1)         elif ws &gt; hs &gt;= aws:             cur_dir = (1, 0)         elif ws &lt;= hs &lt; 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()  <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<div class=\"spoiler\"><b class=\"spoiler_title\">smooth.py<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"python\">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 &gt;= 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)  <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<div class=\"spoiler\"><b class=\"spoiler_title\">worm.kv<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"plaintext\">&lt;Form&gt;:     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  &lt;Worm&gt;:   &lt;Cell&gt;:     canvas:         Color:             rgba: self.color          Rectangle:             size: self.graphical_size             pos: self.graphical_pos <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/div>\n<\/div>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u041a\u043e\u0434, \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043d\u044b\u0439 @tshirtman<\/b><\/p>\n<div class=\"spoiler_text\">\u041c\u043e\u0439 \u043a\u043e\u0434 \u0431\u044b\u043b \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d tshirtman, \u043e\u0434\u043d\u0438\u043c \u0438\u0437 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432 Kivy, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0438\u043b \u043c\u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044e Point \u0432\u043c\u0435\u0441\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0438\u0434\u0436\u0435\u0442\u0430 \u043d\u0430 \u043a\u0430\u0436\u0434\u0443\u044e \u043a\u043b\u0435\u0442\u043a\u0443. \u041e\u0434\u043d\u0430\u043a\u043e \u043c\u043d\u0435 \u043d\u0435 \u043a\u0430\u0436\u0435\u0442\u0441\u044f \u0441\u0435\u0439 \u043a\u043e\u0434 \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u043e\u0441\u0442\u044b\u043c \u0434\u043b\u044f \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u0447\u0435\u043c \u043c\u043e\u0439, \u0445\u043e\u0442\u044f \u043e\u043d \u0442\u043e\u0447\u043d\u043e \u043b\u0443\u0447\u0448\u0435 \u0432 \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 UI \u0438 gamedev. \u0412 \u043e\u0431\u0449\u0435\u043c, \u0432\u043e\u0442 \u043a\u043e\u0434:  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">main.py<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"python\">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 &gt; hs and aws &gt; hs:             cur_dir = (0, -1)         elif ws &gt; hs &gt;= aws:             cur_dir = (1, 0)         elif ws &lt;= hs &lt; 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  # \u041d\u0415 \u0417\u0410\u0411\u0423\u0414\u042c\u0422\u0415, \u0427\u0422\u041e CELL_SIZE - MARGIN \u0414\u041e\u041b\u0416\u041d\u041e \u0414\u0415\u041b\u0418\u0422\u042c\u0421\u042f \u041d\u0410 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() <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">smooth.py<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"python\">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 &gt;= 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) <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">worm.kv<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"plaintext\">&lt;Form&gt;:     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  &lt;Worm&gt;:     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 <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<\/div>\n<\/div>\n<p>  \u0417\u0430\u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043b\u044e\u0431\u044b\u0435 \u0432\u043e\u043f\u0440\u043e\u0441\u044b.<\/p><\/div>\n<p>               <script class=\"js-mediator-script\">!function(e){function t(t,n){if(!(n in e)){for(var r,a=e.document,i=a.scripts,o=i.length;o--;)if(-1!==i[o].src.indexOf(t)){r=i[o];break}if(!r){r=a.createElement(\"script\"),r.type=\"text\/javascript\",r.async=!0,r.defer=!0,r.src=t,r.charset=\"UTF-8\";var d=function(){var e=a.getElementsByTagName(\"script\")[0];e.parentNode.insertBefore(r,e)};\"[object Opera]\"==e.opera?a.addEventListener?a.addEventListener(\"DOMContentLoaded\",d,!1):e.attachEvent(\"onload\",d):d()}}}t(\"\/\/mediator.mail.ru\/script\/2820404\/\",\"_mediator\")}(window);<\/script>     <br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/466249\/\"> https:\/\/habr.com\/ru\/post\/466249\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text-html js-mediator-article\">\u041f\u0440\u0438\u0432\u0435\u0442!<\/p>\n<p>  \u041c\u043d\u043e\u0433\u043e \u043b\u044e\u0434\u0435\u0439 \u0445\u043e\u0442\u0435\u043b\u0438 \u0431\u044b \u043d\u0430\u0447\u0430\u0442\u044c \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430 \u0430\u043d\u0434\u0440\u043e\u0438\u0434, \u043d\u043e Android Studio \u0438 Java \u0438\u0445 \u043e\u0442\u043f\u0443\u0433\u0438\u0432\u0430\u044e\u0442. \u041f\u043e\u0447\u0435\u043c\u0443? \u041f\u043e\u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u044d\u0442\u043e \u0432 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0441\u043c\u044b\u0441\u043b\u0435 \u0438\u0437 \u043f\u0443\u0448\u043a\u0438 \u043f\u043e \u0432\u043e\u0440\u043e\u0431\u044c\u044f\u043c. \u00ab\u042f \u043b\u0438\u0448\u044c \u0445\u043e\u0447\u0443 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0437\u043c\u0435\u0439\u043a\u0443, \u0438 \u0432\u0441\u0435!\u00bb<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/gb\/yu\/vz\/gbyuvzpeftcwxbxi9gwa2xz5axe.png\"><\/p>\n<p>  \u041d\u0430\u0447\u043d\u0435\u043c! (\u0431\u043e\u043d\u0443\u0441 \u0432 \u043a\u043e\u043d\u0446\u0435)  <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-294269","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/294269","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=294269"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/294269\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=294269"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=294269"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=294269"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}