Skip to content

Commit b62e25a

Browse files
committed
Refactor
1 parent e0e975e commit b62e25a

File tree

5 files changed

+122
-53
lines changed

5 files changed

+122
-53
lines changed

block.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,6 @@ def __init__(self):
2323
self.timeDelay = 0
2424
self.target = None
2525
self.substack = set()
26+
27+
def getInputValue(self, inputId, lookIn=(1, 1)):
28+
return self.inputs[inputId.upper()][lookIn[0]][lookIn[1]]

main.py

Lines changed: 74 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,57 +24,91 @@
2424
import scratch
2525
import pygame
2626
import tkinter as tk
27+
from pathlib import Path
2728
# import zipfile as zf
2829
from tkinter.messagebox import *
2930
import os
3031
from targetSprite import TargetSprite
3132

32-
version = "M5"
33+
VERSION = "M6"
3334

34-
# Prepare project file
35+
# Change this to a different project file
36+
PROJECT = "projects/forever.sb3"
37+
38+
# Get project data and create sprites
39+
targets, currentBgFile, project = s2p_unpacker.sb3_unpack(PROJECT)
3540
allSprites = pygame.sprite.Group()
36-
projectToLoad = "projects/forever.sb3" # change this to load a different project
37-
targets, currentBgFile, project = s2p_unpacker.sb3_unpack(projectToLoad)
3841
for t in targets:
3942
sprite = TargetSprite(t)
4043
t.sprite = sprite
4144
allSprites.add(sprite)
42-
wn = tk.Tk() # Start tkinter for popups
43-
wn.withdraw() # Hide main tkinter window
44-
pygame.init() # Start pygame
45+
46+
# Start tkinter for showing some popups, and hide main window
47+
wn = tk.Tk()
48+
wn.withdraw()
49+
50+
# Start Pygame, load fonts and print a debug message
51+
pygame.init()
52+
font = pygame.font.SysFont("sans-serif", 16)
53+
fontXl = pygame.font.SysFont("sans-serif", 36)
4554
scratch.startProject()
55+
56+
# Create paused message
57+
paused = fontXl.render("Paused (Press F6 to resume)", 1, (0, 0, 0))
58+
pausedWidth, pausedHeight = fontXl.size("Paused (Press F6 to resume)")
59+
4660
# Set player size
4761
HEIGHT = 360
4862
WIDTH = 480
49-
projectName = projectToLoad[:-4] # Set the project name for use in the titlebar
63+
64+
# Get project name and set icon
65+
projectName = Path(PROJECT).stem
5066
icon = pygame.image.load("icon.png")
67+
68+
# Create project player and window
5169
display = pygame.display.set_mode([WIDTH, HEIGHT])
52-
pygame.display.set_caption(projectName + " - Scratch2Python")
70+
pygame.display.set_caption(projectName + " - Scratch2Python" + " " + VERSION)
5371
pygame.display.set_icon(icon)
72+
73+
# Get background image
5474
currentBg = scratch.loadSvg(currentBgFile)
55-
# currentBgFile = project.read(target["costumes"][target["currentCostume"]]["md5ext"])
75+
76+
# Set running state
5677
projectRunning = True
78+
isPaused = False
5779

80+
# Initialize clock
5881
clock = pygame.time.Clock()
82+
83+
# Clear display
5984
display.fill((255, 255, 255))
85+
86+
# Create a block execution queue
6087
toExecute = []
6188

89+
# Start green flag scripts
6290
for s in allSprites:
6391
for _, block in s.target.blocks.items():
6492
if block.opcode == "event_whenflagclicked":
6593
print("DEBUG: Running opcode", block.opcode)
6694
print("DEBUG: Running ID", block.blockID)
6795
nextBlock = scratch.execute(block, block.target.sprite)
96+
# Error-proof by checking if the scripts are not empty
6897
if nextBlock:
98+
# Add the next block to the queue
6999
toExecute.append(nextBlock)
70100

101+
# Display the background
71102
scratch.setBackground(currentBg, display)
103+
104+
# Mainloop
72105
while projectRunning:
106+
# Process Pygame events
73107
for event in pygame.event.get():
74108
# Window quit (ALT-F4 / X button)
75109
if event.type == pygame.QUIT:
76110
projectRunning = False
77-
# Some controls
111+
# Debug and utility functions
78112
keys = pygame.key.get_pressed()
79113
if keys[pygame.K_F1]: # Help
80114
showinfo("Work in progress", "Help not currently available")
@@ -85,37 +119,42 @@
85119
print(project.namelist())
86120
if keys[pygame.K_F4]: # Project info
87121
showinfo("Work in progress", "Project info coming soon")
88-
if keys[pygame.K_F5]: # Project info
122+
if keys[pygame.K_F5]: # Extract
89123
confirm = askyesno("Extract", "Extract all project files?")
90124
if confirm:
91125
print("DEBUG: Extracting project")
92126
shutil.rmtree("assets")
93127
os.mkdir("assets")
94128
project.extractall("assets")
129+
if keys[pygame.K_F6]: # Pause
130+
isPaused = not isPaused
95131
display.fill((255, 255, 255))
96-
scratch.setBackground(currentBg, display)
97-
# Move all sprites to current position and direction, run blocks
98-
nextBlocks = []
99-
for block in toExecute:
100-
if block.waiting:
101-
print("DEBUG: Block execution time is", block.executionTime, "delay is", block.timeDelay)
102-
block.executionTime += clock.get_time()
103-
if block.executionTime >= block.timeDelay:
104-
block.waiting = False
105-
block.blockRan = True
106-
nextBlocks.append(block.target.blocks[block.next])
107-
block.executionTime, block.timeDelay = 0, 0
108-
print("DEBUG: Wait period ended")
109-
if not block.blockRan:
110-
print("DEBUG: Running opcode", block.opcode)
111-
print("DEBUG: Running ID", block.blockID)
112-
nextBlock = scratch.execute(block, block.target.sprite)
113-
if nextBlock:
114-
print("DEBUG: Next block is", nextBlock.opcode)
115-
nextBlocks.append(nextBlock)
116-
toExecute = nextBlocks[:]
117-
allSprites.draw(display)
118-
allSprites.update()
132+
if not isPaused:
133+
scratch.setBackground(currentBg, display)
134+
# Move all sprites to current position and direction, run blocks
135+
nextBlocks = []
136+
for block in toExecute:
137+
if block.waiting:
138+
print("DEBUG: Block execution time is", block.executionTime, "delay is", block.timeDelay)
139+
block.executionTime += clock.get_time()
140+
if block.executionTime >= block.timeDelay:
141+
block.waiting = False
142+
block.blockRan = True
143+
nextBlocks.append(block.target.blocks[block.next])
144+
block.executionTime, block.timeDelay = 0, 0
145+
print("DEBUG: Wait period ended")
146+
if not block.blockRan:
147+
print("DEBUG: Running opcode", block.opcode)
148+
print("DEBUG: Running ID", block.blockID)
149+
nextBlock = scratch.execute(block, block.target.sprite)
150+
if nextBlock:
151+
print("DEBUG: Next block is", nextBlock.opcode)
152+
nextBlocks.append(nextBlock)
153+
toExecute = nextBlocks[:]
154+
allSprites.draw(display)
155+
allSprites.update()
156+
else:
157+
display.blit(paused, (WIDTH // 2 - pausedWidth // 2, WIDTH // 2 - pausedHeight // 2))
119158
pygame.display.flip()
120159
wn.update()
121160
clock.tick(30)

s2p_unpacker.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,36 @@ def sb3_unpack(sb3):
2020
if not Path(sb3).exists():
2121
print("ERROR: Project file does not exist")
2222
exit(1)
23+
2324
print("DEBUG: Loading project")
2425
project = zf.ZipFile(sb3, "r")
2526
project_json = json.loads(project.read("project.json"))
2627
targets = []
28+
2729
print("DEBUG: Project JSON output:", project_json)
2830
# Generate the dictionary based on the contents of project.json
2931
for target_obj in project_json['targets']:
32+
# If stage, get background
3033
if target_obj["isStage"]:
3134
current_bg_file = project.read(target_obj["costumes"][target_obj["currentCostume"]]["assetId"] + "." + target_obj["costumes"][target_obj["currentCostume"]]["dataFormat"])
3235
t = target.Target()
36+
37+
# Set sprite values
3338
if "x" in target_obj:
3439
t.x = target_obj["x"]
3540
t.y = target_obj["y"]
3641
t.direction = target_obj["direction"]
3742
t.currentCostume = target_obj["currentCostume"]
43+
44+
# Get costumes
3845
for costume_obj in target_obj["costumes"]:
3946
c = costume.Costume()
4047
if "md5ext" in costume_obj:
4148
c.md5ext = costume_obj["md5ext"]
4249
c.file = project.read(costume_obj["assetId"] + "." + costume_obj["dataFormat"])
4350
t.costumes.append(c)
51+
52+
# Set blocks to their correct values
4453
for block_id, block_obj in target_obj["blocks"].items():
4554
b = block.Block()
4655
b.blockID = block_id
@@ -55,5 +64,7 @@ def sb3_unpack(sb3):
5564
b.target = t
5665
t.blocks[block_id] = b
5766
targets.append(t)
67+
68+
# Send to main.py
5869
return targets, current_bg_file, project
5970

scratch.py

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ def loadSvg(svg_bytes):
1616

1717
# Render a sprite at its coordinates
1818
def render(sprite, x, y, direction, display):
19-
# set direction
19+
# Set direction
2020
sprite = pygame.transform.rotate(sprite, 90 - direction)
21-
# convert Scratch coordinates into Pygame coordinates
21+
# Convert Scratch coordinates into Pygame coordinates
2222
finalX = x + WIDTH // 2 - sprite.get_width() // 2
2323
finalY = HEIGHT // 2 - y - sprite.get_height() // 2
2424
display.blit(sprite, (finalX, finalY))
@@ -29,41 +29,56 @@ def setBackground(bg, display):
2929
render(bg, 0, 0, 90, display)
3030

3131

32+
# Project start event
3233
def startProject():
3334
print("DEBUG: Project start event")
3435

3536

3637
# Run the given block object
3738
def execute(block, s):
38-
# Get block properties
39+
# Get block values
3940
opcode = block.opcode
4041
id = block.blockID
4142
blockRan = block.blockRan
4243
inputs = block.inputs
4344
fields = block.fields
4445
nextBlock = None
45-
if opcode == "motion_gotoxy":
46-
s.setXy(int(inputs["X"][1][1]), int(inputs["Y"][1][1]))
47-
if opcode == "motion_setx":
48-
s.setXy(int(inputs["X"][1][1]), s.y)
49-
if opcode == "motion_changexby":
50-
s.setXy(s.x + int(inputs["DX"][1][1]), s.y)
51-
if opcode == "motion_sety":
52-
s.setXy(s.x, int(inputs["Y"][1][1]))
53-
if opcode == "motion_changeyby":
54-
s.setXy(s.x, s.y + int(inputs["DY"][1][1]))
55-
if opcode == "control_wait":
46+
47+
if opcode == "motion_gotoxy": # go to x: () y: ()
48+
s.setXy(int(block.getInputValue("x")), int(block.getInputValue("y")))
49+
50+
if opcode == "motion_setx": # set x to ()
51+
s.setXy(int(block.getInputValue("x")), s.y)
52+
53+
if opcode == "motion_changexby": # change x by ()
54+
s.setXy(s.x + int(block.getInputValue("x")), s.y)
55+
56+
if opcode == "motion_sety": # set y to ()
57+
s.setXy(s.x, int(block.getInputValue("y")))
58+
59+
if opcode == "motion_changeyby": # change y by ()
60+
s.setXy(s.x, s.y + int(block.getInputValue("y")))
61+
62+
if opcode == "control_wait": # wait () seconds
63+
# If not already waiting
5664
if not block.waiting:
57-
block.timeDelay = int(round(float(inputs["DURATION"][1][1]) * 1000))
65+
# Get time delay and convert it to milliseconds
66+
block.timeDelay = int(round(float(int(block.getInputValue("duration"))) * 1000))
5867
block.waiting = True
5968
block.executionTime = 0
6069
print("DEBUG: Waiting for", block.timeDelay, "ms")
6170
return block
62-
if opcode == "event_whenflagclicked":
71+
72+
if opcode == "event_whenflagclicked": # when green flag clicked
6373
pass
64-
if opcode == "control_forever":
74+
75+
if opcode == "control_forever": # forever {}
76+
# Don't mark the loop as ran
6577
block.blockRan = False
78+
79+
# If there are blocks, get them
6680
if inputs["SUBSTACK"][1]:
81+
# No blocks will be flagged as ran inside a forever loop
6782
for b in block.substack:
6883
s.target.blocks[b].blockRan = False
6984
nextBlock = s.target.blocks[inputs["SUBSTACK"][1]]
@@ -78,6 +93,8 @@ def execute(block, s):
7893
block.substack.add(nb.blockID)
7994
nb.next = block.blockID
8095
return nextBlock
96+
97+
# If there is a block below, print some debug messages and return it, otherwise
8198
if block.next:
8299
print("DEBUG: Next ID", block.next)
83100
nextBlock = s.target.blocks[block.next]

targetSprite.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ class TargetSprite(pygame.sprite.Sprite):
88
def __init__(self, target):
99
pygame.sprite.Sprite.__init__(self)
1010
self.target = target
11-
# l
1211
# Load and upscale
1312
sprite = scratch.loadSvg(target.costumes[target.currentCostume].file)
1413
sprite = pygame.transform.rotate(sprite, 90 - target.direction)

0 commit comments

Comments
 (0)