import logging import random from kivy.clock import Clock from kivy.uix.label import Label from kivy.core.window import Window from kivy.graphics import Ellipse, Rectangle, Color from kivy.uix.screenmanager import Screen from kivy.properties import ( NumericProperty, ListProperty, StringProperty, ObjectProperty, BooleanProperty, ) from .base import BaseModule, Schema, fields logger = logging.getLogger(__name__) class SnakeCell(Label): type = StringProperty("head") def __init__(self, *args, **kwargs): super(SnakeCell, self).__init__(*args, **kwargs) with self.canvas: self.c = Color() self.rect = Rectangle() self.bind( x=self.update_rect, y=self.update_rect, size=self.update_rect, pos=self.update_rect, ) def update_rect(self, *args): if self.type == "head": self.c.rgba = (1.0, 0, 0, 1.0) elif self.type == "fruit": self.c.rgba = (1.0, 1.0, 0, 1.0) else: self.c.rgba = (1.0, 1.0, 1.0, 1.0) self.rect.size = sz = [15, 15] self.rect.pos = [ (self.x + 0.5) * self.parent.width / self.parent.col_width - sz[0] / 2, ((self.parent.col_height - self.y - 1) + 0.5) * self.parent.height / self.parent.col_height - sz[0] / 2, ] def clone(self, **kwargs): return SnakeCell(x=self.x, y=self.y, **kwargs) def collide(self, other): return self.x == other.x and self.y == other.y def __repr__(self): return "" % (self.x, self.y) class SnakeWidget(Label): interval = None direction = ObjectProperty([1, 0]) head = ObjectProperty(None) tail = ObjectProperty([]) fruits = ObjectProperty([]) tail_length = NumericProperty(5) col_width = NumericProperty(16) col_height = NumericProperty(10) speed = NumericProperty(3) running = BooleanProperty(False) def __init__(self, *args, **kwargs): super(SnakeWidget, self).__init__(*args, **kwargs) self._keyboard = Window.request_keyboard(None, self, "text") self._keyboard.bind(on_key_down=self._on_keyboard_down) self.bind( pos=self.update_rects, size=self.update_rects, ) def reset(self): self.remove_widget(self.head) for t in self.tail: self.remove_widget(t) for f in self.fruits: self.remove_widget(f) self.tail = [] self.fruits = [] self.head = SnakeCell(x=8, y=5) self.add_widget(self.head, index=0) self.add_fruit() def on_parent(self, *args): self.reset() def on_running(self, *args): print("on_running", self.running, self.speed) if self.running and not self.interval: self.interval = Clock.schedule_interval(self.tick, 1 / self.speed) elif not self.running and self.interval: self.interval.cancel() self.interval = None def update_rects(self, *args): for c in self.fruits + self.tail + [self.head]: c.update_rect() def tick(self, dt): n = self.head.clone(type="tail") self.tail.insert(0, n) self.add_widget(n, index=1) n.update_rect() for to_rem in self.tail[self.tail_length :]: self.remove_widget(to_rem) self.tail = self.tail[: self.tail_length] self.head.x = (self.head.x + self.direction[0]) % self.col_width self.head.y = (self.head.y + self.direction[1]) % self.col_height if any(self.head.collide(t) for t in self.tail): self.text = "collision!" self.running = False # self.interval.cancel() for f in self.fruits[::-1]: if self.head.collide(f): self.fruits.remove(f) self.remove_widget(f) self.tail_length += 1 self.add_fruit() def _on_keyboard_down(self, widget, event, *args): directions = { "up": (0, -1), "down": (0, 1), "left": (-1, 0), "right": (1, 0), } if event[1] in directions: new_dir = directions[event[1]] old_dir = self.direction if new_dir[0] == -old_dir[0] or old_dir[1] == -new_dir[1]: return self.direction = new_dir def add_fruit(self): for n in range(5): c = SnakeCell( x=random.randint(0, self.col_width - 1), y=random.randint(0, self.col_height - 1), type="fruit", ) if any(c.collide(o) for o in self.tail + self.fruits + [self.head]): continue self.fruits.append(c) self.add_widget(c) c.update_rect() break class SnakeModule(BaseModule): class ConfigSchema(Schema): speed = fields.Integer(missing=5) target_score = fields.Integer(missing=8) start_length = fields.Integer(missing=2) def __init__(self, *args, **kwargs): super(SnakeModule, self).__init__(*args, **kwargs) logger.info("Initializing...") def build(self, parent): self.screen = Screen(name="snek") print(self.config) self.widget = SnakeWidget(tail_length=self.config["start_length"]) self.widget.speed = self.config["speed"] # self.widget.running = True self.update_running_state() self.bind( finished=lambda w, v: self.update_running_state(), enabled=lambda w, v: self.update_running_state(), ) self.widget.bind(tail_length=self.on_score) self.screen.add_widget(self.widget) parent.ids["sm"].add_widget(self.screen) Clock.schedule_interval(self.tick, 0.2) self.sm = parent.ids["sm"] def tick(self, dt): if self.enabled: self.sm.switch_to(self.screen) def on_score(self, widget, score): print("on_score", score) if score >= self.config["target_score"]: self.finished = True def update_running_state(self): self.widget.running = self.enabled and not self.finished