215 lines
6.2 KiB
Python

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 "<SnakeCell(%d,%d)>" % (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