From d8600bba95d81f532017913b8b78fe0132c666df Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 13 Aug 2022 21:12:49 +0200 Subject: [PATCH 1/7] Working version without cursor size change --- test_kivy_draw.py | 303 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100755 test_kivy_draw.py diff --git a/test_kivy_draw.py b/test_kivy_draw.py new file mode 100755 index 0000000..53878e9 --- /dev/null +++ b/test_kivy_draw.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +from random import random, randint, choice +from datetime import timedelta +from kivy.app import App +from kivy.uix.widget import Widget +from kivy.uix.button import Button, ButtonBehavior +from kivy.uix.label import Label +from kivy.graphics import Color, Ellipse, Line, Rectangle +from kivy.core.window import Window +from kivy.uix.boxlayout import BoxLayout +from kivy.clock import Clock +from kivy.uix.popup import Popup +from kivy.uix.colorpicker import ColorPicker +from kivy.uix.scrollview import ScrollView +from kivy.properties import StringProperty, BooleanProperty +from kivy.lang import Builder + + +PASS_PENALTY = 4 +DRAWING_TIME = 60 + + +Builder.load_string(''' +: + Label: + size_hint_y: None + height: self.texture_size[1] + text_size: self.width, None + text: root.text + markup: root.markup +''') + +class ScrollableLabel(ScrollView): + text = StringProperty('') + markup = BooleanProperty(False) + + + +class MyPaintWidget(Label): + _game_state = {"game_started": False, + "game_paused": False, + "game_finished": False + } + _game_wordlists = {"possible_wordlist": set(), + "found_words": set(), + "unfound_words": set() + } + pen_characteristics = {"current_color": (0,0,0,1), + "current_width": 4 + } + current_word = "" + + time = timedelta(seconds=DRAWING_TIME) + _popup = None + + def __init__(self, text="", **kwargs): + super().__init__(**kwargs) + self._keyboard = Window.request_keyboard( + self._keyboard_closed, self, 'text') + if self._keyboard.widget: + # If it exists, this widget is a VKeyboard object which you can use + # to change the keyboard layout. + pass + self._keyboard.bind(on_key_down=self._on_keyboard_down) + with open("words.txt", "r") as wordfile: + self._wordlist = (word.strip() for word in wordfile.readlines()) + self._game_wordlists["possible_wordlist"] = set(self._wordlist) + self.reset_game() + + def _keyboard_closed(self): + print('My keyboard has been closed!') + self._keyboard.unbind(on_key_down=self._on_keyboard_down) + self._keyboard = None + + def _on_keyboard_down(self, keyboard, keycode, text, modifiers): + print('The key', keycode, 'have been pressed') + print(' - text is %r' % text) + print(' - modifiers are %r' % modifiers) + if not self._game_state["game_started"]: + if keycode[0] == 32: + self.start_game() + + elif keycode[1] == 'p': + self.toggle_pause() + + elif self.is_game_playing: + if keycode[0] == 32: + self.next_word() + elif keycode[0] == 13: + self.pass_word() + elif keycode[1] == 'z' and "ctrl" in modifiers: + if self.canvas.before.children: + self.canvas.before.remove(self.canvas.before.children[-1]) + self.canvas.before.remove(self.canvas.before.children[-1]) + self.canvas.before.remove(self.canvas.before.children[-1]) + elif self._game_state["game_finished"]: + if keycode[0] == 8: + self.reset_game() + + # Keycode is composed of an integer + a string + # If we hit escape, let the input through to leave the app + if keycode[1] == 'escape': + return False + + # Return True to accept the key. Otherwise, it will be used by + # the system. + return True + + def toggle_pause(self): + self._game_state["game_paused"] = not self._game_state["game_paused"] + + def start_game(self): + self.text="" + self._game_state["game_started"] = True + self._game_state["game_paused"] = False + self.next_word(True) + + def next_word(self, passed=False): + if not passed: + self._game_wordlists["found_words"].add(self.current_word) + self.current_word = choice(list(self._game_wordlists["possible_wordlist"])) + self._game_wordlists["possible_wordlist"].discard(self.current_word) + self.clear_drawing() + + def pass_word(self): + self._game_wordlists["unfound_words"].add(self.current_word) + self.next_word(True) + if self.time - timedelta(seconds=PASS_PENALTY) > timedelta(0): + self.time -= timedelta(seconds=PASS_PENALTY) + else: + self.time = timedelta(0) + + @property + def is_game_playing(self): + return (self._game_state["game_started"] + and not self._game_state["game_paused"] + and not self._game_state["game_finished"]) + + @property + def current_color(self): + return self.pen_characteristics["current_color"] + + def on_touch_down(self, touch): + if self.is_game_playing: + with self.canvas.before: + if self.collide_point(touch.x, touch.y): + Color(*self.pen_characteristics["current_color"], mode='rgba') + diameter = self.pen_characteristics["current_width"] + Ellipse(pos=(touch.x - diameter / 2, touch.y - diameter / 2), + size=(2*diameter, 2*diameter)) + touch.ud['line'] = Line(points=(touch.x, touch.y), width=diameter) + + + + def on_touch_move(self, touch): + if self.is_game_playing: + if 'line' in touch.ud: + if self.collide_point(touch.x, touch.y): + touch.ud['line'].points += [touch.x, touch.y] + elif self.collide_point(touch.ud['line'].points[-2], touch.y): + touch.ud['line'].points += [touch.ud['line'].points[-2], touch.y] + elif self.collide_point(touch.x, touch.ud['line'].points[-1]): + touch.ud['line'].points += [touch.x, touch.ud['line'].points[-1]] + + def recap(self): + found_words = self._game_wordlists['found_words'] + unfound_words =self._game_wordlists['unfound_words'] + lines = ["[b]Tour terminé ![/b]\n"+ + f"Trouvés: [color=#5BB834]{len(found_words)}[/color]", + "-"+ "\n-".join(found_words) if found_words else "", + f"Passés: [color=#FF2A40]{len(unfound_words)}[/color]", + "- "+ "\n- ".join(unfound_words) if unfound_words else ""] + self.clear_drawing() + self._popup = Popup(title='Fini !', + content=ScrollableLabel(text="[size=24]"+"\n".join(lines)+"[/size]", + markup=True), + size_hint=(0.6,0.6), + auto_dismiss=False) + self._popup.open() + + def game_finished(self): + self._game_state["game_finished"] = True + self.recap() + + def reset_game(self): + if self._popup: + self._popup.dismiss() + self.color = (0,0,0,1) + self.text = "Appuyer sur 'espace' pour commencer à jouer" + self.time = timedelta(seconds=DRAWING_TIME) + self._game_state["game_finished"] = False + self._game_state["game_started"] = False + + def clear_drawing(self): + self.canvas.before.clear() + +class SizeButton(Widget, ButtonBehavior): + def __init__(self, size=1, **kwargs): + super().__init__(**kwargs) + self.dot_size = size + with self.canvas: + Color(0,0,0, mode="rgb") + self.bg = Rectangle(pos=self.pos, size=self.size) + Color(1,1,1, mode="rgb") + self.dot = Ellipse(pos=(self.center_x-size/2, self.center_y-size/2), + size = (size*2, size*2)) + def redraw(self, args): + self.dot.pos = (self.center_x-size/2, self.center_y-size/2) + self.bg.pos = self.pos + self.bg.size = self.size + self.bind(size=redraw, pos=redraw) + + +class MyPaintApp(App): + state = False + init = True + + def build(self): + Window.clearcolor = (1, 1, 1, 1) + #self.init_keyboard() + parent = BoxLayout(orientation='vertical') + + self.painter = MyPaintWidget(size_hint=(0.9, 1)) + Clock.schedule_interval(self.update_time, 1.0/10.0) + clearbtn = Button(text='Clear') + clearbtn.bind(on_release=self.clear_canvas) + togglebtn = Button(text='Inverser la couleur du fond') + togglebtn.bind(on_release=self.toggle_background) + + self.right_row = BoxLayout(orientation="vertical", size_hint=(0.1,1)) + color_picker_button = Button(text="Couleur") + color_picker_button.background_color = self.painter.pen_characteristics["current_color"] + color_picker_button.background_normal = '' + color_picker_button.bind(on_release=self.open_color_picker) + self.right_row.add_widget(color_picker_button) + for i in range(1,6): + size_button = SizeButton(2*i) + self.right_row.add_widget(size_button) + + middle_row = BoxLayout(orientation="horizontal", size_hint=(1, 0.8)) + middle_row.add_widget(self.painter) + middle_row.add_widget(self.right_row) + + bottom_row = BoxLayout(orientation='horizontal', spacing=5, size_hint=(1, .1)) + bottom_row.add_widget(togglebtn) + bottom_row.add_widget(clearbtn) + + top_row = BoxLayout(orientation='horizontal', spacing=5, size_hint=(1, .1)) + self.word_label = Label(text="Mot:", color=[0,0,0,1], halign="left") + self.time_label = Label(text=f"{self.painter.time}", color=[0,0,0,1]) + top_row.add_widget(self.word_label) + top_row.add_widget(self.time_label) + + parent.add_widget(top_row) + parent.add_widget(middle_row) + parent.add_widget(bottom_row) + return parent + + def open_color_picker(self, _): + clr_picker = ColorPicker() + clr_picker.color = self.painter.pen_characteristics["current_color"] + def on_color_change(instance, value): + self.painter.pen_characteristics["current_color"] = value + self.right_row.children[-1].background_color = self.painter.current_color + clr_picker.bind(color=on_color_change) + popup = Popup(title="Couleur du pinceau", + content=clr_picker, + size_hint=(0.5,0.5)) + popup.open() + + def update_time(self, _): + self.word_label.text_size = (self.word_label.size[0], None) + if self.painter.is_game_playing: + if self.painter.time > timedelta(0): + self.painter.time -= timedelta(milliseconds=100) + self.word_label.text = f"Mot: {self.painter.current_word}" + else: + self.painter.game_finished() + + self.time_label.text = f"{self.painter.time}" + + def toggle_background(self, obj): + + if self.state: + Window.clearcolor = (1, 1, 1, 1) + self.painter.pen_characteristics["current_color"] = (0,0,0,1) + self.word_label.color = (0,0,0,1) + self.time_label.color = (0,0,0,1) + self.state = False + else: + Window.clearcolor = (0,0,0,1) + self.painter.pen_characteristics["current_color"] = (1,1,1,1) + self.word_label.color = (1,1,1,1) + self.time_label.color = (1,1,1,1) + self.state = True + self.clear_canvas(obj) + + def clear_canvas(self, _): + self.painter.clear_drawing() + + +if __name__ == '__main__': + MyPaintApp().run() From c46f8a809e35206ec55a757ce7b8ac882d2ba5f5 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 13 Aug 2022 21:46:39 +0200 Subject: [PATCH 2/7] Add color and size change for pen --- test_kivy_draw.py | 58 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/test_kivy_draw.py b/test_kivy_draw.py index 53878e9..71edb32 100755 --- a/test_kivy_draw.py +++ b/test_kivy_draw.py @@ -39,7 +39,9 @@ class ScrollableLabel(ScrollView): class MyPaintWidget(Label): _game_state = {"game_started": False, "game_paused": False, - "game_finished": False + "game_finished": False, + "word passed": timedelta(0), + "word found": timedelta(0) } _game_wordlists = {"possible_wordlist": set(), "found_words": set(), @@ -49,7 +51,7 @@ class MyPaintWidget(Label): "current_width": 4 } current_word = "" - + message_label = None time = timedelta(seconds=DRAWING_TIME) _popup = None @@ -118,6 +120,33 @@ class MyPaintWidget(Label): def next_word(self, passed=False): if not passed: self._game_wordlists["found_words"].add(self.current_word) + if self.message_label and self.current_word: + if passed: + self.message_label.color = '#FF2A40' + self.message_label.text = f'"{self.current_word}" a été passé !' + else: + self.message_label.color = '#5BB834' + self.message_label.text = f'"{self.current_word}" a été trouvé !' + + self.current_word = choice(list(self._game_wordlists["possible_wordlist"])) + self._game_wordlists["possible_wordlist"].discard(self.current_word) + self.clear_drawing() + + def pass_word(self): + self._game_wordlists["unfound_words"].add(self.current_word) + self.next_word(True) + if self.time - timedelta(seconds=PASS_PENALTY) > timedelta(0): + self.time -= timedelta(seconds=PASS_PENALTY) + else: + self.time = timedelta(0) + + @property + def is_game_playing(self): + return (self._game_state["game_started"] + and not self._game_state["game_paused"] + and not self._game_state["game_finished"]) + + self.current_word = choice(list(self._game_wordlists["possible_wordlist"])) self._game_wordlists["possible_wordlist"].discard(self.current_word) self.clear_drawing() @@ -194,10 +223,11 @@ class MyPaintWidget(Label): def clear_drawing(self): self.canvas.before.clear() -class SizeButton(Widget, ButtonBehavior): - def __init__(self, size=1, **kwargs): +class SizeButton(ButtonBehavior, Widget): + def __init__(self, callback, size=1, **kwargs): super().__init__(**kwargs) self.dot_size = size + self.callback = callback with self.canvas: Color(0,0,0, mode="rgb") self.bg = Rectangle(pos=self.pos, size=self.size) @@ -210,6 +240,8 @@ class SizeButton(Widget, ButtonBehavior): self.bg.size = self.size self.bind(size=redraw, pos=redraw) + def on_release(self): + self.callback(self.dot_size) class MyPaintApp(App): state = False @@ -234,7 +266,7 @@ class MyPaintApp(App): color_picker_button.bind(on_release=self.open_color_picker) self.right_row.add_widget(color_picker_button) for i in range(1,6): - size_button = SizeButton(2*i) + size_button = SizeButton(self.set_cursor_size, 2*i) self.right_row.add_widget(size_button) middle_row = BoxLayout(orientation="horizontal", size_hint=(1, 0.8)) @@ -246,16 +278,27 @@ class MyPaintApp(App): bottom_row.add_widget(clearbtn) top_row = BoxLayout(orientation='horizontal', spacing=5, size_hint=(1, .1)) - self.word_label = Label(text="Mot:", color=[0,0,0,1], halign="left") + self.word_label = Label(text="Mot:", color=[0,0,0,1], halign="left", valign="top") + def redraw(self, obj): + self.text_size = obj + self.word_label.bind(size=redraw) + self.time_label = Label(text=f"{self.painter.time}", color=[0,0,0,1]) + message_label = Label(color=[0,0,0,1]) top_row.add_widget(self.word_label) + top_row.add_widget(message_label) top_row.add_widget(self.time_label) + self.painter.message_label = message_label parent.add_widget(top_row) parent.add_widget(middle_row) parent.add_widget(bottom_row) return parent + def set_cursor_size(self, size): + print(size) + self.painter.pen_characteristics["current_width"] = size + def open_color_picker(self, _): clr_picker = ColorPicker() clr_picker.color = self.painter.pen_characteristics["current_color"] @@ -269,7 +312,6 @@ class MyPaintApp(App): popup.open() def update_time(self, _): - self.word_label.text_size = (self.word_label.size[0], None) if self.painter.is_game_playing: if self.painter.time > timedelta(0): self.painter.time -= timedelta(milliseconds=100) @@ -284,12 +326,14 @@ class MyPaintApp(App): if self.state: Window.clearcolor = (1, 1, 1, 1) self.painter.pen_characteristics["current_color"] = (0,0,0,1) + self.right_row.children[-1].background_color = self.painter.current_color self.word_label.color = (0,0,0,1) self.time_label.color = (0,0,0,1) self.state = False else: Window.clearcolor = (0,0,0,1) self.painter.pen_characteristics["current_color"] = (1,1,1,1) + self.right_row.children[-1].background_color = self.painter.current_color self.word_label.color = (1,1,1,1) self.time_label.color = (1,1,1,1) self.state = True From a5d25ada57829345043e2e8a8c522da499fbd02e Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 13 Aug 2022 22:57:37 +0200 Subject: [PATCH 3/7] Add config menu, and supoprt for csv input file with word type --- test_kivy_draw.py | 128 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 95 insertions(+), 33 deletions(-) diff --git a/test_kivy_draw.py b/test_kivy_draw.py index 71edb32..218f0d2 100755 --- a/test_kivy_draw.py +++ b/test_kivy_draw.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +import re +import csv from random import random, randint, choice from datetime import timedelta from kivy.app import App @@ -8,17 +10,18 @@ from kivy.uix.label import Label from kivy.graphics import Color, Ellipse, Line, Rectangle from kivy.core.window import Window from kivy.uix.boxlayout import BoxLayout +from kivy.uix.gridlayout import GridLayout from kivy.clock import Clock from kivy.uix.popup import Popup from kivy.uix.colorpicker import ColorPicker from kivy.uix.scrollview import ScrollView from kivy.properties import StringProperty, BooleanProperty from kivy.lang import Builder - +from kivy.uix.textinput import TextInput PASS_PENALTY = 4 DRAWING_TIME = 60 - +WORDS_FILE = "words.txt" Builder.load_string(''' : @@ -35,6 +38,19 @@ class ScrollableLabel(ScrollView): markup = BooleanProperty(False) +class NumberInput(TextInput): + def __init__(self, init_value, **kwargs): + self.init_value = init_value + kwargs["text"] = str(init_value) + super().__init__(**kwargs) + + pat = r"^[0-9]+$" + def insert_text(self, substring, from_undo=False): + if not re.match(self.pat, substring): + s='' + else: + s = substring + super().insert_text(s, from_undo=from_undo) class MyPaintWidget(Label): _game_state = {"game_started": False, @@ -43,7 +59,7 @@ class MyPaintWidget(Label): "word passed": timedelta(0), "word found": timedelta(0) } - _game_wordlists = {"possible_wordlist": set(), + _game_wordlists = {"possible_wordlist": dict(), "found_words": set(), "unfound_words": set() } @@ -57,18 +73,34 @@ class MyPaintWidget(Label): def __init__(self, text="", **kwargs): super().__init__(**kwargs) - self._keyboard = Window.request_keyboard( - self._keyboard_closed, self, 'text') - if self._keyboard.widget: - # If it exists, this widget is a VKeyboard object which you can use - # to change the keyboard layout. - pass - self._keyboard.bind(on_key_down=self._on_keyboard_down) - with open("words.txt", "r") as wordfile: - self._wordlist = (word.strip() for word in wordfile.readlines()) - self._game_wordlists["possible_wordlist"] = set(self._wordlist) + self._keyboard = None + self._get_keyboard() + self._load_words() self.reset_game() + def _load_words(self): + if WORDS_FILE.endswith(".csv"): + # read a CSV file + with open(WORDS_FILE, "r", newline='') as wordfile: + my_reader = csv.reader(wordfile) + self._game_wordlists["possible_wordlist"] = {line[0]: + line[1] for line in my_reader} + else: + # read a plain text file + with open(WORDS_FILE, "r") as wordfile: + self._wordlist = (word.strip() for word in wordfile.readlines()) + self._game_wordlists["possible_wordlist"] = dict.fromkeys(self._wordlist) + + def _get_keyboard(self): + if self._keyboard is None: + self._keyboard = Window.request_keyboard( + self._keyboard_closed, self, 'text') + if self._keyboard.widget: + # If it exists, this widget is a VKeyboard object which you can use + # to change the keyboard layout. + pass + self._keyboard.bind(on_key_down=self._on_keyboard_down) + def _keyboard_closed(self): print('My keyboard has been closed!') self._keyboard.unbind(on_key_down=self._on_keyboard_down) @@ -81,6 +113,8 @@ class MyPaintWidget(Label): if not self._game_state["game_started"]: if keycode[0] == 32: self.start_game() + elif keycode[1] == 'c': + self.open_config() elif keycode[1] == 'p': self.toggle_pause() @@ -129,26 +163,10 @@ class MyPaintWidget(Label): self.message_label.text = f'"{self.current_word}" a été trouvé !' self.current_word = choice(list(self._game_wordlists["possible_wordlist"])) - self._game_wordlists["possible_wordlist"].discard(self.current_word) - self.clear_drawing() + value = self._game_wordlists["possible_wordlist"].pop(self.current_word) + if value: + self.message_label.text += '\n'+f'Le prochain mot est de type: {value}' - def pass_word(self): - self._game_wordlists["unfound_words"].add(self.current_word) - self.next_word(True) - if self.time - timedelta(seconds=PASS_PENALTY) > timedelta(0): - self.time -= timedelta(seconds=PASS_PENALTY) - else: - self.time = timedelta(0) - - @property - def is_game_playing(self): - return (self._game_state["game_started"] - and not self._game_state["game_paused"] - and not self._game_state["game_finished"]) - - - self.current_word = choice(list(self._game_wordlists["possible_wordlist"])) - self._game_wordlists["possible_wordlist"].discard(self.current_word) self.clear_drawing() def pass_word(self): @@ -191,6 +209,48 @@ class MyPaintWidget(Label): elif self.collide_point(touch.x, touch.ud['line'].points[-1]): touch.ud['line'].points += [touch.x, touch.ud['line'].points[-1]] + def open_config(self): + layout = GridLayout() + layout.cols=2 + layout.add_widget(Label(text="Chemin vers la liste de mots")) + layout.add_widget(TextInput(text=WORDS_FILE, multiline=False)) + layout.add_widget(Label(text="Temps de dessin\n(secondes")) + layout.add_widget(NumberInput(DRAWING_TIME, multiline=False)) + layout.add_widget(Label(text="Temps de pénalité\nquand on passe\n(secondes)")) + layout.add_widget(NumberInput(PASS_PENALTY, multiline=False)) + conf_popup = Popup(title='Configuration', content=layout, size_hint=(0.5, 0.5)) + conf_popup.bind(on_dismiss=self.apply_conf) + conf_popup.open() + + def apply_conf(self, obj): + global DRAWING_TIME + global PASS_PENALTY + global WORDS_FILE + try: + drawing_time = int(obj.content.children[2].text) + if drawing_time > 0: + DRAWING_TIME = drawing_time + except ValueError: + Popup(title="Error", content=Label(text="Mauvaise valeur pour temps de dessin")).open() + try: + pass_penalty = int(obj.content.children[0].text) + if pass_penalty > 0 and pass_penalty < drawing_time: + PASS_PENALTY = pass_penalty + except ValueError: + Popup(title="Error", content=Label(text="Mauvaise valeur pour pénalité")).open() + self.reset_game() + if obj.content.children[4].text != WORDS_FILE: + old_file = WORDS_FILE + WORDS_FILE = obj.content.children[4].text + try: + self._load_words() + except OSError: + Popup(title="Error", content=Label(text=f"Erreur de lecture du fichier '{WORDS_FILE}'")).open() + WORDS_FILE = old_file + self._get_keyboard() + + + def recap(self): found_words = self._game_wordlists['found_words'] unfound_words =self._game_wordlists['unfound_words'] @@ -214,6 +274,8 @@ class MyPaintWidget(Label): def reset_game(self): if self._popup: self._popup.dismiss() + if self.message_label: + self.message_label.text = "" self.color = (0,0,0,1) self.text = "Appuyer sur 'espace' pour commencer à jouer" self.time = timedelta(seconds=DRAWING_TIME) @@ -284,7 +346,7 @@ class MyPaintApp(App): self.word_label.bind(size=redraw) self.time_label = Label(text=f"{self.painter.time}", color=[0,0,0,1]) - message_label = Label(color=[0,0,0,1]) + message_label = Label(color=[0,0,0,1], halign="center") top_row.add_widget(self.word_label) top_row.add_widget(message_label) top_row.add_widget(self.time_label) From 9af04d3c3072be598dfff2b2263af5802ce12cb6 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 14 Aug 2022 13:53:15 +0200 Subject: [PATCH 4/7] Add some other features --- test_kivy_draw.py | 93 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 22 deletions(-) diff --git a/test_kivy_draw.py b/test_kivy_draw.py index 218f0d2..94612ef 100755 --- a/test_kivy_draw.py +++ b/test_kivy_draw.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 import re +import os import csv from random import random, randint, choice -from datetime import timedelta +from datetime import timedelta, datetime from kivy.app import App from kivy.uix.widget import Widget from kivy.uix.button import Button, ButtonBehavior @@ -21,7 +22,7 @@ from kivy.uix.textinput import TextInput PASS_PENALTY = 4 DRAWING_TIME = 60 -WORDS_FILE = "words.txt" +WORDS_FILE = "mots.csv" Builder.load_string(''' : @@ -66,6 +67,7 @@ class MyPaintWidget(Label): pen_characteristics = {"current_color": (0,0,0,1), "current_width": 4 } + current_round = 0 current_word = "" message_label = None time = timedelta(seconds=DRAWING_TIME) @@ -74,22 +76,38 @@ class MyPaintWidget(Label): def __init__(self, text="", **kwargs): super().__init__(**kwargs) self._keyboard = None + + with self.canvas.before: + Color(1,1,1, mode='rgb') + self.bg = Rectangle(pos=self.pos, size=self.size) + def resize_bg(self, obj): + self.bg.size = self.size + self.bg.pos = self.pos + self.bind(size=resize_bg, pos=resize_bg) + self._get_keyboard() self._load_words() + self._output_dir = datetime.now().isoformat(timespec='minutes').replace(":","-") + "_creations" + self._fullscreen = False self.reset_game() def _load_words(self): - if WORDS_FILE.endswith(".csv"): - # read a CSV file - with open(WORDS_FILE, "r", newline='') as wordfile: - my_reader = csv.reader(wordfile) - self._game_wordlists["possible_wordlist"] = {line[0]: - line[1] for line in my_reader} - else: - # read a plain text file - with open(WORDS_FILE, "r") as wordfile: - self._wordlist = (word.strip() for word in wordfile.readlines()) - self._game_wordlists["possible_wordlist"] = dict.fromkeys(self._wordlist) + try: + if WORDS_FILE.endswith(".csv"): + # read a CSV file + with open(WORDS_FILE, "r", newline='', encoding="utf-8") as wordfile: + my_reader = csv.reader(wordfile) + self._game_wordlists["possible_wordlist"] = {line[0]: + line[1] for line in my_reader} + else: + # read a plain text file + with open(WORDS_FILE, "r", encoding="utf-8") as wordfile: + self._wordlist = (word.strip() for word in wordfile.readlines()) + self._game_wordlists["possible_wordlist"] = dict.fromkeys(self._wordlist) + except OSError: + Popup(title="Error", content=Label(text=f"Erreur de lecture du fichier '{WORDS_FILE}'")).open() + self._game_wordlists["possible_wordlist"] = dict() + def _get_keyboard(self): if self._keyboard is None: @@ -125,14 +143,22 @@ class MyPaintWidget(Label): elif keycode[0] == 13: self.pass_word() elif keycode[1] == 'z' and "ctrl" in modifiers: - if self.canvas.before.children: - self.canvas.before.remove(self.canvas.before.children[-1]) - self.canvas.before.remove(self.canvas.before.children[-1]) - self.canvas.before.remove(self.canvas.before.children[-1]) + if self.canvas.after.children: + self.canvas.after.remove(self.canvas.after.children[-1]) + self.canvas.after.remove(self.canvas.after.children[-1]) + self.canvas.after.remove(self.canvas.after.children[-1]) elif self._game_state["game_finished"]: if keycode[0] == 8: self.reset_game() + if keycode[0] == 292: + print(Window.fullscreen) + if self._fullscreen: + Window.fullscreen = False + else: + Window.fullscreen = "auto" + self._fullscreen = not self._fullscreen + # Keycode is composed of an integer + a string # If we hit escape, let the input through to leave the app if keycode[1] == 'escape': @@ -149,6 +175,7 @@ class MyPaintWidget(Label): self.text="" self._game_state["game_started"] = True self._game_state["game_paused"] = False + self.current_round += 1 self.next_word(True) def next_word(self, passed=False): @@ -161,7 +188,10 @@ class MyPaintWidget(Label): else: self.message_label.color = '#5BB834' self.message_label.text = f'"{self.current_word}" a été trouvé !' - + if not self._game_wordlists["possible_wordlist"]: + self.time = timedelta(0) + return + self.save_frame() self.current_word = choice(list(self._game_wordlists["possible_wordlist"])) value = self._game_wordlists["possible_wordlist"].pop(self.current_word) if value: @@ -177,6 +207,14 @@ class MyPaintWidget(Label): else: self.time = timedelta(0) + def save_frame(self): + if self.current_word: + if not os.path.isdir(self._output_dir): + os.mkdir(self._output_dir) + filename = os.path.join(self._output_dir, + f"round-{self.current_round}_" + os.path.normcase(self.current_word) + ".png") + self.export_to_png(filename) + @property def is_game_playing(self): return (self._game_state["game_started"] @@ -189,7 +227,7 @@ class MyPaintWidget(Label): def on_touch_down(self, touch): if self.is_game_playing: - with self.canvas.before: + with self.canvas.after: if self.collide_point(touch.x, touch.y): Color(*self.pen_characteristics["current_color"], mode='rgba') diameter = self.pen_characteristics["current_width"] @@ -259,6 +297,7 @@ class MyPaintWidget(Label): "-"+ "\n-".join(found_words) if found_words else "", f"Passés: [color=#FF2A40]{len(unfound_words)}[/color]", "- "+ "\n- ".join(unfound_words) if unfound_words else ""] + self.save_frame() self.clear_drawing() self._popup = Popup(title='Fini !', content=ScrollableLabel(text="[size=24]"+"\n".join(lines)+"[/size]", @@ -278,12 +317,16 @@ class MyPaintWidget(Label): self.message_label.text = "" self.color = (0,0,0,1) self.text = "Appuyer sur 'espace' pour commencer à jouer" + self.current_word = "" + self._game_wordlists["found_words"] = set() + self._game_wordlists["unfound_words"] = set() + self.time = timedelta(seconds=DRAWING_TIME) self._game_state["game_finished"] = False self._game_state["game_started"] = False def clear_drawing(self): - self.canvas.before.clear() + self.canvas.after.clear() class SizeButton(ButtonBehavior, Widget): def __init__(self, callback, size=1, **kwargs): @@ -305,7 +348,7 @@ class SizeButton(ButtonBehavior, Widget): def on_release(self): self.callback(self.dot_size) -class MyPaintApp(App): +class GuessWhatIDrawApp(App): state = False init = True @@ -387,6 +430,9 @@ class MyPaintApp(App): if self.state: Window.clearcolor = (1, 1, 1, 1) + with self.painter.canvas.before: + Color(1,1,1, mode='rgb') + self.painter.bg = Rectangle(pos=self.painter.pos, size=self.painter.size) self.painter.pen_characteristics["current_color"] = (0,0,0,1) self.right_row.children[-1].background_color = self.painter.current_color self.word_label.color = (0,0,0,1) @@ -394,6 +440,9 @@ class MyPaintApp(App): self.state = False else: Window.clearcolor = (0,0,0,1) + with self.painter.canvas.before: + Color(0,0,0, mode='rgb') + self.painter.bg = Rectangle(pos=self.painter.pos, size=self.painter.size) self.painter.pen_characteristics["current_color"] = (1,1,1,1) self.right_row.children[-1].background_color = self.painter.current_color self.word_label.color = (1,1,1,1) @@ -406,4 +455,4 @@ class MyPaintApp(App): if __name__ == '__main__': - MyPaintApp().run() + GuessWhatIDrawApp().run() From 0b0aea6d86f8132d695817ab2d9716e70d156ed2 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 14 Apr 2024 22:01:10 +0200 Subject: [PATCH 5/7] Various improvements --- .gitignore | 6 ++++++ pictionnary.ico | Bin 0 -> 766 bytes test_kivy_draw.py | 35 +++++++++++++++++++++++++---------- 3 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 pictionnary.ico diff --git a/.gitignore b/.gitignore index f8b73e7..0e0ad79 100644 --- a/.gitignore +++ b/.gitignore @@ -138,3 +138,9 @@ dmypy.json # Cython debug symbols cython_debug/ + +*_creations/ +*.exe +*.zip +*.csv +*.txt diff --git a/pictionnary.ico b/pictionnary.ico new file mode 100644 index 0000000000000000000000000000000000000000..160cae6b499687b0e4ea82480e5eb44acb00e44e GIT binary patch literal 766 zcmcgqyAHxI3_NNUHo7oZ?ZW8dl>vz_;j`HIJpQ6wnmP}us)U3{>o#|`&vq1G$EWL# z{O<8?fD?c-t)ty&f!|v9>jAIl1Jg8Zg{}JR`=wInsG=h1M#;?K$VuFdt!0&n%>$HT zV>?vX`aaO}Qlof*#RPWg2lo>zN(@tFLP}@l8mmYaU>zaRxM0AnC@#P@Id2vSIJ{ml nj*;-vHrXhk@gNcoYLF1+0^H6ATHx5aceD)ho8RiC_#^oN#1LVC literal 0 HcmV?d00001 diff --git a/test_kivy_draw.py b/test_kivy_draw.py index 94612ef..2ec99fa 100755 --- a/test_kivy_draw.py +++ b/test_kivy_draw.py @@ -67,6 +67,7 @@ class MyPaintWidget(Label): pen_characteristics = {"current_color": (0,0,0,1), "current_width": 4 } + bg = None current_round = 0 current_word = "" message_label = None @@ -77,6 +78,7 @@ class MyPaintWidget(Label): super().__init__(**kwargs) self._keyboard = None + self._load_words() with self.canvas.before: Color(1,1,1, mode='rgb') self.bg = Rectangle(pos=self.pos, size=self.size) @@ -86,7 +88,6 @@ class MyPaintWidget(Label): self.bind(size=resize_bg, pos=resize_bg) self._get_keyboard() - self._load_words() self._output_dir = datetime.now().isoformat(timespec='minutes').replace(":","-") + "_creations" self._fullscreen = False self.reset_game() @@ -105,7 +106,11 @@ class MyPaintWidget(Label): self._wordlist = (word.strip() for word in wordfile.readlines()) self._game_wordlists["possible_wordlist"] = dict.fromkeys(self._wordlist) except OSError: - Popup(title="Error", content=Label(text=f"Erreur de lecture du fichier '{WORDS_FILE}'")).open() + errmsg = f"Erreur de lecture du fichier '{WORDS_FILE}'" + if self.bg: + Popup(title="Error", content=Label(text=errmsg)).open() + else: + print(errmsg) self._game_wordlists["possible_wordlist"] = dict() @@ -374,22 +379,25 @@ class GuessWhatIDrawApp(App): size_button = SizeButton(self.set_cursor_size, 2*i) self.right_row.add_widget(size_button) - middle_row = BoxLayout(orientation="horizontal", size_hint=(1, 0.8)) + middle_row = BoxLayout(orientation="horizontal", size_hint=(1, 0.75)) middle_row.add_widget(self.painter) middle_row.add_widget(self.right_row) - bottom_row = BoxLayout(orientation='horizontal', spacing=5, size_hint=(1, .1)) + bottom_row = BoxLayout(orientation='horizontal', spacing=5, size_hint=(1, .05)) bottom_row.add_widget(togglebtn) bottom_row.add_widget(clearbtn) - top_row = BoxLayout(orientation='horizontal', spacing=5, size_hint=(1, .1)) - self.word_label = Label(text="Mot:", color=[0,0,0,1], halign="left", valign="top") + top_row = BoxLayout(orientation='horizontal', spacing=5, size_hint=(1, .2)) + self.word_label = Label(text="Mot:", color=[0,0,0,1], halign="left", valign="top", size_hint=(0.2,1)) def redraw(self, obj): - self.text_size = obj + self.text_size = self.size self.word_label.bind(size=redraw) - self.time_label = Label(text=f"{self.painter.time}", color=[0,0,0,1]) - message_label = Label(color=[0,0,0,1], halign="center") + self.time_label = Label(text=f"{self.painter.time}", color=[0,0,0,1], + font_size='50pt', size_hint=(0.2,1)) + message_label = Label(color=[0,0,0,1], halign="center", valign="top", + font_size='40pt', size_hint=(0.6,1)) + message_label.bind(size=redraw) top_row.add_widget(self.word_label) top_row.add_widget(message_label) top_row.add_widget(self.time_label) @@ -424,7 +432,7 @@ class GuessWhatIDrawApp(App): else: self.painter.game_finished() - self.time_label.text = f"{self.painter.time}" + self.time_label.text = f"{formatTimedelta(self.painter.time)}" def toggle_background(self, obj): @@ -453,6 +461,13 @@ class GuessWhatIDrawApp(App): def clear_canvas(self, _): self.painter.clear_drawing() +def formatTimedelta(time:timedelta): + seconds = int(time.total_seconds()) + minutes, reminder = divmod(seconds, 60) + + return f"{minutes:02}:{reminder:02}.{int(time.microseconds/10000):02}" + + if __name__ == '__main__': GuessWhatIDrawApp().run() From 4af26766bfc98ecfc4d92a21effd9c62abaef707 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 14 Apr 2024 22:02:33 +0200 Subject: [PATCH 6/7] Rename main file --- test_kivy_draw.py => guess_what_I_draw.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test_kivy_draw.py => guess_what_I_draw.py (100%) diff --git a/test_kivy_draw.py b/guess_what_I_draw.py similarity index 100% rename from test_kivy_draw.py rename to guess_what_I_draw.py From 56f0648d88c11e9cad28b9eb694ba388e4bc93db Mon Sep 17 00:00:00 2001 From: saxodwarf Date: Sun, 14 Apr 2024 22:38:53 +0200 Subject: [PATCH 7/7] Add more information to readme.md --- README.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index eddbf3d..de66104 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,61 @@ -# Devinddessin +# Devine-dessin +Jeu permettant de dessiner un mot choisi au hasard dans une liste. + +Tant que le jeu n'est pas quitté, les mots de la liste ne sont pas proposé à nouveau. + +## Liste de mot + +La liste de mot peut être au format texte brut, avec un mot par ligne, ou au format CSV. + +Si la liste est au format CSV, la première colonne doit contenir le mot à dessiner, et la seconde colonne peut contenir un indice. +Les colonnes du fichier CSV ne doivent pas avoir de titre. + +Par défaut, le jeu cherche une liste dans un fichier du dossier courant nommé `mots.csv`. + +## Installation + +Le jeu n'est pour l'instant pas packagé. +Pour le lancer: + +- Construire une liste de mot comme présenté plus haut +- Créer un venv : `python -m venv ` +- Activer le venv : `. /bin/activate` +- Installer les dépendances : `pip install -r requirements.txt` +- Récupérer le fichier `guess_what_I_draw.py` +- Lancer le jeu en exécutant le fichier. + + +## Contrôles: + +### Tout le temps: +- **`Échap` :** Quitter le jeu immédiatement + +### Quand le jeu n'est pas lancé: +- **`c`:** Ouvrir le menu de configuration +- **`Espace` :** Lancer la manche + +### Quand le jeu est en cours: +- **Clic gauche** dans la zone de dessin : dessiner +- **`Ctrl + z` :** Annuler le dernier trait +- **`Entrer` :** Passer le mot en cours (une pénalité de temps est appliquée) +- **`Espace` :** Valider le mot en cours comme "trouvé" +- **`p` :** Activer/Désactiver la pause. + +### Quand la manche est écoulée: +Le menu de récapitulation est affiché. +- **`Backspace` :** Passer au tour suivant + +## Configuration +Dans le menu de configuration, il est possible de configurer : +- Le chemin vers la liste de mot ; +- La durée d'une manche ; +- Le temps de pénalité appliqué pour chaque mot passé. + +Une fois la configuration effectuée, vous pouvez l'appliquer en cliquant en dehors de la pop-up du menu + + +## Dessins +Les dessins sont sauvegardés pour la postérité dans un dossier créé dans le répertoire courant, nommé `T_creations`. + +Le nom des fichiers de dessin sont au format `round-_.png`, avec `i` le numéro de la manche.