pystage 0.0.16__tar.gz → 0.1.0.dev0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {pystage-0.0.16/src/pystage.egg-info → pystage-0.1.0.dev0}/PKG-INFO +14 -2
  2. {pystage-0.0.16 → pystage-0.1.0.dev0}/README.md +9 -0
  3. {pystage-0.0.16 → pystage-0.1.0.dev0}/setup.cfg +2 -2
  4. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/convert/code_writer.py +14 -0
  5. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/convert/sb3.py +81 -44
  6. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/convert/sb3_templates.py +16 -0
  7. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/_control.py +1 -1
  8. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/_looks.py +1 -1
  9. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/_motion.py +2 -1
  10. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/_operators.py +17 -4
  11. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/_sensing.py +35 -13
  12. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/gui.py +5 -0
  13. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/stage.py +36 -4
  14. {pystage-0.0.16 → pystage-0.1.0.dev0/src/pystage.egg-info}/PKG-INFO +14 -2
  15. {pystage-0.0.16 → pystage-0.1.0.dev0}/LICENSE +0 -0
  16. {pystage-0.0.16 → pystage-0.1.0.dev0}/pyproject.toml +0 -0
  17. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/__init__.py +0 -0
  18. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/convert/__init__.py +0 -0
  19. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/__init__.py +0 -0
  20. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/_base_sprite.py +0 -0
  21. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/_events.py +0 -0
  22. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/_pen.py +0 -0
  23. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/_sound.py +0 -0
  24. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/_variables.py +0 -0
  25. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/asking.py +0 -0
  26. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/assets.py +0 -0
  27. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/code_block.py +0 -0
  28. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/constants.py +0 -0
  29. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/messages.py +0 -0
  30. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/monitors.py +0 -0
  31. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/core/sprite.py +0 -0
  32. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/de/__init__.py +0 -0
  33. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/de/sprite.py +0 -0
  34. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/de/stage.py +0 -0
  35. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/en/__init__.py +0 -0
  36. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/en/sprite.py +0 -0
  37. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/en/stage.py +0 -0
  38. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/fonts/roboto-bold.ttf +0 -0
  39. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/fonts/roboto-light.ttf +0 -0
  40. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/fonts/roboto-regular.ttf +0 -0
  41. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/images/grid.svg +0 -0
  42. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/images/pen.png +0 -0
  43. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/images/say.png +0 -0
  44. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/images/think.png +0 -0
  45. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/images/zombie_idle.png +0 -0
  46. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/l10n/__init__.py +0 -0
  47. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/l10n/__main__.py +0 -0
  48. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/l10n/api.py +0 -0
  49. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/l10n/translations.py +0 -0
  50. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/sounds/dancehead2.wav +0 -0
  51. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/sounds/guitar.wav +0 -0
  52. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage/util.py +0 -0
  53. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage.egg-info/SOURCES.txt +0 -0
  54. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage.egg-info/dependency_links.txt +0 -0
  55. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage.egg-info/requires.txt +0 -0
  56. {pystage-0.0.16 → pystage-0.1.0.dev0}/src/pystage.egg-info/top_level.txt +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pystage
3
- Version: 0.0.16
3
+ Version: 0.1.0.dev0
4
4
  Summary: Scratch-like Python programming
5
5
  Home-page: https://github.com/pystage/pystage
6
6
  Author: Kai Eckert
7
- Author-email: eckert@hdm-stuttgart.de
7
+ Author-email: k.eckert@hs-mannheim.de
8
8
  License: MIT
9
9
  Project-URL: Bug Tracker, https://github.com/pystage/pystage/issues
10
10
  Classifier: Programming Language :: Python :: 3
@@ -13,6 +13,9 @@ Classifier: Operating System :: OS Independent
13
13
  Requires-Python: >=3.6
14
14
  Description-Content-Type: text/markdown
15
15
  License-File: LICENSE
16
+ Requires-Dist: pygame
17
+ Requires-Dist: jinja2
18
+ Requires-Dist: svglib
16
19
 
17
20
  # PyStage
18
21
 
@@ -89,6 +92,15 @@ And here is the result:
89
92
  # Installation
90
93
  ```pip install pystage```
91
94
 
95
+ **if doesn't work, try following commands one by one.**
96
+ ```bash
97
+ pip install wheel
98
+ pip install pygame==2.3.0
99
+ pip install svglib==1.1.0
100
+ pip install reportlab==3.5.67
101
+ pip install pystage
102
+ ```
103
+
92
104
  # Getting started
93
105
 
94
106
  If you are interested in PyStage and want to get in touch, feel free to join us on our [Discord Server](https://discord.gg/MSGrs3UCCz).
@@ -73,6 +73,15 @@ And here is the result:
73
73
  # Installation
74
74
  ```pip install pystage```
75
75
 
76
+ **if doesn't work, try following commands one by one.**
77
+ ```bash
78
+ pip install wheel
79
+ pip install pygame==2.3.0
80
+ pip install svglib==1.1.0
81
+ pip install reportlab==3.5.67
82
+ pip install pystage
83
+ ```
84
+
76
85
  # Getting started
77
86
 
78
87
  If you are interested in PyStage and want to get in touch, feel free to join us on our [Discord Server](https://discord.gg/MSGrs3UCCz).
@@ -1,8 +1,8 @@
1
1
  [metadata]
2
2
  name = pystage
3
- version = 0.0.16
3
+ version = 0.1.0.dev
4
4
  author = Kai Eckert
5
- author_email = eckert@hdm-stuttgart.de
5
+ author_email = k.eckert@hs-mannheim.de
6
6
  description = Scratch-like Python programming
7
7
  long_description = file: README.md
8
8
  long_description_content_type = text/markdown
@@ -63,6 +63,7 @@ class CodeWriter():
63
63
  self.jinja_environment.filters["global_sound"] = lambda name: self.global_sound(name)
64
64
  self.jinja_environment.filters["global_costume"] = lambda name: self.global_costume(name)
65
65
  self.jinja_environment.filters["global_backdrop"] = lambda name: self.global_backdrop(name)
66
+ self.jinja_environment.filters["global_sprite"] = lambda name: self.get_sprite_var(unquoted(name))
66
67
 
67
68
  logger.debug("CodeWriter created.")
68
69
 
@@ -269,6 +270,19 @@ class CodeWriter():
269
270
  context["CURRENT_SPRITE"] = self.get_sprite_var()
270
271
  if "{{ID}}" in text:
271
272
  context["ID"] = self.get_id()
273
+
274
+ # if {{NEXT | indent (4)}} is still in text, but there is no
275
+ # NEXT in context, then it means the Scratch “hat” block is empty,
276
+ # so set NEXT to "pass", and the function is essentially empty.
277
+ if "indent(4)" in text:
278
+ if "NEXT" in text and not context.get("NEXT"):
279
+ context["NEXT"] = "pass"
280
+ if "SUBSTACK" in text and not context.get("SUBSTACK"):
281
+ context["SUBSTACK"] = "pass"
282
+ if "SUBSTACK2" in text and not context.get("SUBSTACK2"):
283
+ context["SUBSTACK2"] = "pass"
284
+ if "{{CONDITION}}" in text and not context.get("CONDITION"):
285
+ context["CONDITION"] = "None"
272
286
  template = self.jinja_environment.from_string(text)
273
287
  try:
274
288
  text = template.render(context)
@@ -149,6 +149,9 @@ def get_block(block, blocks, stage):
149
149
  res["params"][f] = f'"{block["fields"][f][0]}"'
150
150
  for i in block["inputs"]:
151
151
  value = block["inputs"][i][1]
152
+ if not value:
153
+ # empty if condition
154
+ continue
152
155
  if isinstance(value, list):
153
156
  res["params"][i] = get_input_value(value, stage)
154
157
  else:
@@ -179,6 +182,12 @@ def unique_global_name(name, ext):
179
182
  global_names.add(f"{iname}.{ext}")
180
183
  return iname, ext
181
184
 
185
+ def get_sb3_data(filename):
186
+ archive = zipfile.ZipFile(filename, 'r')
187
+ with archive.open("project.json") as f:
188
+ data = json.loads(f.read())
189
+ return data
190
+
182
191
  def get_intermediate(data, name):
183
192
  hat_blocks = [
184
193
  "event_whenthisspriteclicked",
@@ -287,7 +296,7 @@ def get_intermediate(data, name):
287
296
  return project
288
297
 
289
298
 
290
- def get_python(project, language="core"):
299
+ def get_python(project, language="core", project_link=None):
291
300
  lang_module = importlib.import_module(f"pystage.{language}")
292
301
  sprite_class = lang_module.sprite_class.__name__
293
302
  stage_class = lang_module.stage_class.__name__
@@ -297,7 +306,12 @@ def get_python(project, language="core"):
297
306
  add_variable = get_translated_function("pystage_makevariable", language)
298
307
  create_sprite = get_translated_function("pystage_createsprite", language, stage=True)
299
308
  play = get_translated_function("pystage_play", language, stage=True)
300
- res = textwrap.dedent(f'''\
309
+ res = ""
310
+ if project_link:
311
+ res += textwrap.dedent(f'''\
312
+ # Online Converter, URL: {project_link}
313
+ ''')
314
+ res += textwrap.dedent(f'''\
301
315
  # {project['name']} (pyStage, converted from Scratch 3)
302
316
 
303
317
  from pystage.{language} import {sprite_class}, {stage_class}
@@ -339,6 +353,12 @@ def get_python(project, language="core"):
339
353
  sprite_var = writer.get_sprite_var()
340
354
  costumes = [(writer.global_costume(c["local_name"], False), c) for c in sprite["costumes"]]
341
355
  sounds = [writer.global_sound(s["local_name"], False) for s in sprite["sounds"]]
356
+
357
+ # Generate initialization code for each sprite.
358
+ res += textwrap.dedent(f"""\
359
+
360
+ # Create and initialize sprite '{sprite_var}'
361
+ """)
342
362
  res += textwrap.dedent(f'''\
343
363
  {sprite_var} = {stage_var}.{create_sprite}(None)
344
364
  {sprite_var}.{get_translated_function("pystage_setname", language)}("{sprite["name"]}")
@@ -399,6 +419,15 @@ def get_python(project, language="core"):
399
419
  res += textwrap.dedent(f'''\
400
420
  {sprite_var}.{get_translated_function("pystage_setmonitorstyle_large", language)}("{monitor["variable"]}")
401
421
  ''')
422
+
423
+ # Now generate the code for each sprite.
424
+ for sprite in project["sprites"]:
425
+ writer.set_sprite(sprite["name"])
426
+ sprite_var = writer.get_sprite_var()
427
+ res += textwrap.dedent(f"""\
428
+
429
+ # Scratch Blocks for '{sprite_var}'
430
+ """)
402
431
  for block in sprite["blocks"]:
403
432
  res += writer.process(block)
404
433
 
@@ -414,10 +443,36 @@ def print_python(project, language="core"):
414
443
  print(get_python(project, language))
415
444
 
416
445
 
446
+ def create_project(file_name, project_name, project, directory, language="core", project_link=None):
447
+ print(f"Creating project: {project_name}")
448
+ print(f"Exporting to: {directory}")
449
+ archive = zipfile.ZipFile(file_name, 'r')
450
+ dp = Path(directory)
451
+ if dp.exists():
452
+ raise("Directory must not exist at this point, but it does: {}".format(directory))
453
+ os.mkdir(dp)
454
+ os.mkdir(dp / "images")
455
+ os.mkdir(dp / "sounds")
456
+ for key in project["costumes"]:
457
+ c = project["costumes"][key]
458
+ with archive.open(f'{key}.{c["extension"]}') as infile:
459
+ with open(dp / "images" / f'{c["global_name"]}.{c["extension"]}', "wb") as outfile:
460
+ outfile.write(infile.read())
461
+ for key in project["sounds"]:
462
+ s = project["sounds"][key]
463
+ with archive.open(f'{key}.{s["extension"]}') as infile:
464
+ with open(dp / "sounds" / f'{s["global_name"]}.{s["extension"]}', "wb") as outfile:
465
+ outfile.write(infile.read())
466
+ with open(dp / f'{project_name}.py', "w", encoding="utf-8") as pyfile:
467
+ pyfile.write(get_python(project, language=language, project_link=project_link))
468
+
469
+
470
+
417
471
  ##
418
472
  # Main
419
473
  #
420
474
 
475
+
421
476
  if __name__ == "__main__":
422
477
 
423
478
  parser = argparse.ArgumentParser(
@@ -438,47 +493,29 @@ if __name__ == "__main__":
438
493
  elif args.verbose:
439
494
  logging.basicConfig(level=logging.INFO)
440
495
 
441
- archive = zipfile.ZipFile(args.file, 'r')
442
- with archive.open("project.json") as f:
443
- project_name = to_filename(Path(args.file).stem)
444
- data = json.loads(f.read())
445
- if args.sb3_json:
446
- print(json.dumps(data, indent=2))
447
- sys.exit(0)
448
- project = get_intermediate(data, project_name)
449
- if args.intermediate:
450
- print(json.dumps(project, indent=2))
496
+ project_name = to_filename(Path(args.file).stem)
497
+ data = get_sb3_data(args.file)
498
+ if args.sb3_json:
499
+ print(json.dumps(data, indent=2))
500
+ sys.exit(0)
501
+ project = get_intermediate(data, project_name)
502
+ if args.intermediate:
503
+ print(json.dumps(project, indent=2))
504
+ sys.exit(1)
505
+ elif args.python:
506
+ print_python(project, args.language)
507
+ sys.exit(1)
508
+ else:
509
+ directory = project_name
510
+ if args.directory:
511
+ directory = args.directory
512
+ print(f"Exporting to: {directory}")
513
+ dp = Path(directory)
514
+ if dp.exists() and not dp.is_dir:
515
+ print("Output directory exists, but is not a directory. Use -d to specify another directory.")
451
516
  sys.exit(1)
452
- elif args.python:
453
- print_python(project, args.language)
517
+ elif dp.exists() and next(dp.iterdir(), False):
518
+ print("Output directory is not empty. Use -d to specify another one.")
454
519
  sys.exit(1)
455
- else:
456
- print(f"Creating project: {project_name}")
457
- directory = project_name
458
- if args.directory:
459
- directory = args.directory
460
- print(f"Exporting to: {directory}")
461
- dp = Path(directory)
462
- if dp.exists() and not dp.is_dir:
463
- print("Output directory exists, but is not a directory. Use -d to specify another directory.")
464
- sys.exit(1)
465
- elif dp.exists() and next(dp.iterdir(), False):
466
- print("Output directory is not empty. Use -d to specify another one.")
467
- sys.exit(1)
468
- elif not dp.exists():
469
- print(f"Creating directory: {dp}")
470
- os.mkdir(dp)
471
- os.mkdir(dp / "images")
472
- os.mkdir(dp / "sounds")
473
- for key in project["costumes"]:
474
- c = project["costumes"][key]
475
- with archive.open(f'{key}.{c["extension"]}') as infile:
476
- with open(dp / "images" / f'{c["global_name"]}.{c["extension"]}', "wb") as outfile:
477
- outfile.write(infile.read())
478
- for key in project["sounds"]:
479
- s = project["sounds"][key]
480
- with archive.open(f'{key}.{s["extension"]}') as infile:
481
- with open(dp / "sounds" / f'{s["global_name"]}.{s["extension"]}', "wb") as outfile:
482
- outfile.write(infile.read())
483
- with open(dp / f'{project_name}.py', "w", encoding="utf-8") as pyfile:
484
- pyfile.write(get_python(project, language=args.language))
520
+ elif not dp.exists():
521
+ create_project(args.file, project_name, project, directory, args.language)
@@ -42,7 +42,21 @@ templates = {
42
42
  {{CURRENT_SPRITE}}.{{func}}({{func}}_{{ID}})
43
43
 
44
44
  ''',
45
+
46
+ "event_whengreaterthan": '''\
47
+ def {{func}}_{{ID}}(self):
48
+ {{NEXT | indent(4)}}
49
+ {{CURRENT_SPRITE}}.{{func}}({{VALUE}}, {{func}}_{{ID}})
50
+
51
+ ''',
52
+
53
+ "event_whenbackdropswitchesto": '''\
54
+ def {{func}}_{{ID}}(self):
55
+ {{NEXT | indent(4)}}
56
+ {{CURRENT_SPRITE}}.{{func}}({{BACKDROP | global_backdrop}}, {{func}}_{{ID}})
45
57
 
58
+ ''',
59
+
46
60
  "control_start_as_clone": '''\
47
61
 
48
62
  def {{func}}_{{ID}}(self):
@@ -106,6 +120,8 @@ templates = {
106
120
  "sound_sounds_menu": "{{SOUND_MENU | global_sound }}",
107
121
 
108
122
  "sensing_keyoptions": "{{KEY_OPTION}}",
123
+ "sensing_distancetomenu": "{{DISTANCETOMENU | global_sprite}}",
124
+ "sensing_touchingobjectmenu": "{{TOUCHINGOBJECTMENU | global_sprite}}",
109
125
 
110
126
  }
111
127
 
@@ -17,7 +17,7 @@ class _Control(BaseSprite):
17
17
 
18
18
  def control_stop_this(self):
19
19
  # This is equivalent to return
20
- pass
20
+ self.code_manager.current_block.running = False
21
21
  control_stop_this.opcode="control_stop"
22
22
  control_stop_this.param="STOP_OPTION"
23
23
  control_stop_this.value="this script"
@@ -200,7 +200,7 @@ class _LooksSprite(_Looks):
200
200
  pass
201
201
 
202
202
  def looks_think(self, text):
203
- self.bubble_manager.say(text, Bubble.THINK)
203
+ self.bubble_manager.think(text)
204
204
 
205
205
  def looks_switchcostumeto(self, costume):
206
206
  self.costume_manager.switch_costume(costume)
@@ -23,6 +23,7 @@ class _Motion(_Sensing):
23
23
  # Position is stored internally as PyGame coordinates
24
24
  # and refers to the center of the costume.
25
25
  self._pos = pygame.Vector2(0, 0)
26
+ self.draggable = False
26
27
  self.motion_setx(0)
27
28
  self.motion_sety(0)
28
29
 
@@ -61,7 +62,7 @@ class _Motion(_Sensing):
61
62
 
62
63
 
63
64
  def motion_goto_sprite(self, sprite):
64
- self.motion_gotoxy(sprite.x, sprite.y)
65
+ self.motion_gotoxy(sprite.x_position(), sprite.y_position())
65
66
 
66
67
  motion_goto_sprite.opcode = "motion_goto"
67
68
 
@@ -16,15 +16,28 @@ class _Operators():
16
16
  'sqrt': math.sqrt,
17
17
  'sin': lambda n: math.sin((math.pi * n) / 180),
18
18
  'cos': lambda n: math.cos((math.pi * n) / 180),
19
- 'tan': math.tan,
19
+ 'tan': lambda n: math.tan((math.pi * n) / 180) if not n == 90 else float("inf"),
20
20
  'asin': lambda n: (math.asin(n) * 180) / math.pi,
21
21
  'acos': lambda n: (math.acos(n) * 180) / math.pi,
22
22
  'atan': lambda n: (math.atan(n) * 180) / math.pi,
23
- 'ln': math.log,
24
- 'log': math.log,
23
+ 'ln': lambda n : math.log(n) if not n == 0 else -float("inf"),
24
+ 'log': lambda n: math.log10(n) if not n == 0 else -float("inf"),
25
25
  'e ^': math.exp,
26
26
  '10 ^': lambda n: math.pow(10, n),
27
27
  }
28
28
  if operator not in ops:
29
29
  raise ValueError(f"Operator {operator} not supported.")
30
- return ops[operator](number)
30
+
31
+ try:
32
+ value = ops[operator](number)
33
+ if value == float("inf"):
34
+ return "Infinity"
35
+ elif value == -float("inf"):
36
+ return "-Infinity"
37
+ # avoid for situations like sin(30) == 0.49999999999999994
38
+ return round(value, 10)
39
+ except ValueError:
40
+ # print(f"ValueError: math domain error: {operator}({number})")
41
+ return "NaN"
42
+ except OverflowError:
43
+ return "Infinity"
@@ -2,7 +2,8 @@ import pygame
2
2
  import pystage
3
3
  from pystage.core.constants import KEY_MAPPINGS
4
4
  from pystage.core._base_sprite import BaseSprite
5
-
5
+ from math import sqrt
6
+ import datetime
6
7
 
7
8
 
8
9
  class _Sensing(BaseSprite):
@@ -43,14 +44,14 @@ class _Sensing(BaseSprite):
43
44
 
44
45
 
45
46
  def sensing_setdragmode_draggable(self):
46
- pass
47
+ self.draggable = True
47
48
  sensing_setdragmode_draggable.opcode="sensing_setdragmode"
48
49
  sensing_setdragmode_draggable.param="DRAG_MODE"
49
50
  sensing_setdragmode_draggable.value="draggable"
50
51
 
51
52
 
52
53
  def sensing_setdragmode_notdraggable(self):
53
- pass
54
+ self.draggable = False
54
55
  sensing_setdragmode_notdraggable.opcode="sensing_setdragmode"
55
56
  sensing_setdragmode_notdraggable.param="DRAG_MODE"
56
57
  sensing_setdragmode_notdraggable.value="not draggable"
@@ -130,56 +131,68 @@ class _Sensing(BaseSprite):
130
131
  sensing_of_backdropname.value="backdrop name"
131
132
 
132
133
  def sensing_current_year(self):
133
- pass
134
+ return datetime.datetime.now().year
134
135
  sensing_current_year.opcode="sensing_current"
135
136
  sensing_current_year.param="CURRENTMENU"
136
137
  sensing_current_year.value="YEAR"
137
138
 
138
139
 
139
140
  def sensing_current_month(self):
140
- pass
141
+ return datetime.datetime.now().month
141
142
  sensing_current_month.opcode="sensing_current"
142
143
  sensing_current_month.param="CURRENTMENU"
143
144
  sensing_current_month.value="MONTH"
144
145
 
145
146
 
146
147
  def sensing_current_date(self):
147
- pass
148
+ return datetime.datetime.now().day
148
149
  sensing_current_date.opcode="sensing_current"
149
150
  sensing_current_date.param="CURRENTMENU"
150
151
  sensing_current_date.value="DATE"
151
152
 
152
153
 
153
154
  def sensing_current_dayofweek(self):
154
- pass
155
+ current_datetime = datetime.datetime.now().date()
156
+ #This leads to a Sunday=1 based counting
157
+ day_of_week = (current_datetime.isoweekday() % 7) + 1
158
+ return day_of_week
155
159
  sensing_current_dayofweek.opcode="sensing_current"
156
160
  sensing_current_dayofweek.param="CURRENTMENU"
157
161
  sensing_current_dayofweek.value="DAYOFWEEK"
158
162
 
159
163
 
160
164
  def sensing_current_hour(self):
161
- pass
165
+ return datetime.datetime.now().hour
162
166
  sensing_current_hour.opcode="sensing_current"
163
167
  sensing_current_hour.param="CURRENTMENU"
164
168
  sensing_current_hour.value="HOUR"
165
169
 
166
170
 
167
171
  def sensing_current_minute(self):
168
- pass
172
+ return datetime.datetime.now().minute
169
173
  sensing_current_minute.opcode="sensing_current"
170
174
  sensing_current_minute.param="CURRENTMENU"
171
175
  sensing_current_minute.value="MINUTE"
172
176
 
173
177
 
174
178
  def sensing_current_second(self):
175
- pass
179
+ return datetime.datetime.now().second
176
180
  sensing_current_second.opcode="sensing_current"
177
181
  sensing_current_second.param="CURRENTMENU"
178
182
  sensing_current_second.value="SECOND"
179
183
 
180
184
 
181
185
  def sensing_dayssince2000(self):
182
- pass
186
+ # use datetime to find days since 2000 in timezone
187
+ start_date = datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc)
188
+ current_datetime = datetime.datetime.now(datetime.timezone.utc)
189
+ time_difference = current_datetime - start_date
190
+ days = time_difference.days
191
+ # find fraction of current day like in Scratch
192
+ decimal_in_day_string = (current_datetime.hour * 3600 + current_datetime.minute * 60 + current_datetime.second) / (24 * 3600)
193
+ # put full days and part of day together
194
+ since_2000 = days + decimal_in_day_string
195
+ return since_2000
183
196
 
184
197
  def sensing_username(self):
185
198
  # Makes not a lot of sense, maybe for compatibility?
@@ -203,6 +216,7 @@ class _SensingSprite(BaseSprite):
203
216
  sensing_touchingobject_edge.value="_edge_"
204
217
 
205
218
  def sensing_touchingobject_sprite(self, sprite):
219
+ sprite = sprite._core
206
220
  if sprite.rect.colliderect(self.rect):
207
221
  offset = (self.rect.left - sprite.rect.left, self.rect.top - sprite.rect.top)
208
222
  return sprite.mask.overlap(self.mask, offset) is not None
@@ -217,13 +231,21 @@ class _SensingSprite(BaseSprite):
217
231
  pass
218
232
 
219
233
  def sensing_distanceto_pointer(self):
220
- pass
234
+ mx, my = pygame.mouse.get_pos()
235
+ # gets position of sprite
236
+ sprite_rect = self.rect
237
+ # Calculate the distance between the sprite and the mouse pointer
238
+ distance = sqrt((sprite_rect.centerx - mx)**2 + (sprite_rect.centery - my)**2)
239
+ return distance
240
+
221
241
  sensing_distanceto_pointer.opcode="sensing_distanceto"
222
242
  sensing_distanceto_pointer.param="DISTANCETOMENU"
223
243
  sensing_distanceto_pointer.value="_mouse_"
224
244
 
225
245
  def sensing_distanceto_sprite(self, sprite):
226
- pass
246
+ # self is an instance of CoreSprite
247
+ # sprite is an instance of Sprite
248
+ return self._pos.distance_to(sprite._core._pos)
227
249
  sensing_distanceto_sprite.opcode="sensing_distanceto"
228
250
 
229
251
 
@@ -183,11 +183,16 @@ class BubbleManager():
183
183
 
184
184
  def say(self, text: str, border=Bubble.SAY):
185
185
  # print(text)
186
+ if isinstance(text, (int, float)):
187
+ text = str(round(text, 2) if text % 1 > 0 else int(text))
186
188
  if self.bubble:
187
189
  self.bubble.kill()
188
190
  self.bubble = None
189
191
  if text is not None and text.strip() != "":
190
192
  self.bubble = Bubble(self.sprite, text, border)
191
193
  self.sprite.stage.bubbles.add(self.bubble)
194
+
195
+ def think(self, text: str):
196
+ self.say(text, Bubble.THINK)
192
197
 
193
198
 
@@ -50,8 +50,9 @@ class SpriteGroup(pygame.sprite.OrderedUpdates):
50
50
  new_index = index + value
51
51
  if new_index < 0:
52
52
  new_index = 0
53
- if new_index > len(self._spritelist) - 1:
54
- new_index = len(self._spritelist) - 1
53
+ # because the sprite was already removed above, so don't need to minus 1 for the length
54
+ if new_index > len(self._spritelist):
55
+ new_index = len(self._spritelist)
55
56
  self._spritelist.insert(new_index, sprite)
56
57
  # print(self._spritelist)
57
58
 
@@ -141,6 +142,9 @@ class CoreStage(
141
142
 
142
143
  def _update(self, dt):
143
144
  self.code_manager._update(dt)
145
+ # Update it so that the layering is correct while drawing
146
+ # otherwise the no effect for to_front, to_back, etc.
147
+ self._update_visible()
144
148
 
145
149
  def _draw(self, surface: pygame.Surface):
146
150
  surface.fill(self.background_color)
@@ -155,6 +159,11 @@ class CoreStage(
155
159
  This runs the game loop
156
160
  """
157
161
  dt = 0
162
+
163
+ # maybe we should move this to other place
164
+ left_clicking_and_holding = False
165
+ clicked_sprite = None
166
+
158
167
  while self.running:
159
168
  for event in pygame.event.get():
160
169
  if event.type == pygame.QUIT:
@@ -166,9 +175,11 @@ class CoreStage(
166
175
  for sprite in self.sprites:
167
176
  assert(isinstance(sprite, CoreSprite))
168
177
  sprite.code_manager.process_key_pressed(event.key)
169
- if event.type == pygame.MOUSEBUTTONUP:
178
+ # to make it consistent with Scratch, should be BUTTONDOWN
179
+ if event.type == pygame.MOUSEBUTTONDOWN:
170
180
  pos = pygame.Vector2(pygame.mouse.get_pos())
171
- for sprite in self.visible_sprites.sprites()[-1:0:-1]:
181
+ # [-1:0:-1] will reverse the list, but not include 0
182
+ for sprite in self.visible_sprites.sprites()[-1::-1]:
172
183
  assert(isinstance(sprite, CoreSprite))
173
184
  if sprite.rect.collidepoint(pos):
174
185
  internal_pos = pos - sprite.rect.topleft
@@ -180,6 +191,27 @@ class CoreStage(
180
191
  sprite.code_manager.process_click()
181
192
  break
182
193
 
194
+ # with these three conditions, we can detect a click and hold,
195
+ # but not move the mouse while holding
196
+ if event.type == pygame.MOUSEBUTTONDOWN and not left_clicking_and_holding and pygame.mouse.get_pressed()[0]:
197
+ pos = pygame.mouse.get_pos()
198
+ for sprite in filter(lambda s:s.draggable, self.visible_sprites.sprites()[-1::-1]):
199
+ assert(isinstance(sprite, CoreSprite))
200
+ sprite_rect = sprite.image.get_rect()
201
+ sprite_rect.topleft = sprite.rect.topleft
202
+ if sprite_rect.collidepoint(pos):
203
+ clicked_pos = pos[0] - sprite_rect.left, pos[1] - sprite_rect.top
204
+ if sprite.image.get_at(clicked_pos).a != 0:
205
+ left_clicking_and_holding = True
206
+ clicked_sprite = sprite
207
+
208
+ if not pygame.mouse.get_pressed()[0] and left_clicking_and_holding:
209
+ left_clicking_and_holding = False
210
+
211
+ if left_clicking_and_holding:
212
+ assert isinstance(clicked_sprite, CoreSprite)
213
+ clicked_sprite.motion_goto_pointer()
214
+
183
215
  # Handle broadcast messages
184
216
  for message in self.message_broker.get_messages():
185
217
  for sprite in self.sprites:
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pystage
3
- Version: 0.0.16
3
+ Version: 0.1.0.dev0
4
4
  Summary: Scratch-like Python programming
5
5
  Home-page: https://github.com/pystage/pystage
6
6
  Author: Kai Eckert
7
- Author-email: eckert@hdm-stuttgart.de
7
+ Author-email: k.eckert@hs-mannheim.de
8
8
  License: MIT
9
9
  Project-URL: Bug Tracker, https://github.com/pystage/pystage/issues
10
10
  Classifier: Programming Language :: Python :: 3
@@ -13,6 +13,9 @@ Classifier: Operating System :: OS Independent
13
13
  Requires-Python: >=3.6
14
14
  Description-Content-Type: text/markdown
15
15
  License-File: LICENSE
16
+ Requires-Dist: pygame
17
+ Requires-Dist: jinja2
18
+ Requires-Dist: svglib
16
19
 
17
20
  # PyStage
18
21
 
@@ -89,6 +92,15 @@ And here is the result:
89
92
  # Installation
90
93
  ```pip install pystage```
91
94
 
95
+ **if doesn't work, try following commands one by one.**
96
+ ```bash
97
+ pip install wheel
98
+ pip install pygame==2.3.0
99
+ pip install svglib==1.1.0
100
+ pip install reportlab==3.5.67
101
+ pip install pystage
102
+ ```
103
+
92
104
  # Getting started
93
105
 
94
106
  If you are interested in PyStage and want to get in touch, feel free to join us on our [Discord Server](https://discord.gg/MSGrs3UCCz).
File without changes
File without changes