Skip to content

Commit

Permalink
[FIX] Using multiple loops with "if pressed" command (#4028)
Browse files Browse the repository at this point in the history
Will fix #3865,

The Good ✅: This PR makes it possible to encapsulate multiple _if pressed_ commands with a _repeat_, _for loop_ & _while loop_ 
**from level 9 onwards** , notice that this is the general problem that is discovered in #3865.

<details>
  <summary>Some Examples</summary>
  
  ### Level 9
  ```python
repeat 3 times 
    if x is pressed 
        forward 15
        
repeat 3 times 
    if y is pressed 
        forward 15
        
repeat 3 times 
    if z is pressed 
        forward 15
  ```

  ### Level 10
  ```python
animals is dog, cat, blobfish

for animal in animals
    if x is pressed
        print animal
        
for animal in animals
    if y is pressed
        print animal
        
for animal in animals
    if z is pressed
        print animal
  ```

  ### Level 15
  ```python
answer = 0

while answer != 20
    if x is pressed
        answer = answer + 10
        print "Answer raise by 10!"
    
while answer != 60
    if y is pressed
        answer = answer + 20
        print "Answer raise by 20!"
  ```
</details>


The Bad ❗: There is still an issue with the original problem in level 7. For some (unknown) reason when running:
```
repeat 3 times if x is pressed forward 15
repeat 3 times if y is pressed forward 15
repeat 3 times if z is pressed forward 15
```
The parser translates the second line to:
```
if 'y' == 'pressed forward 15': 
``` 
Essentially, a 'normal' if statement.

To make it a bit more complicated, this only happens when there are more than 2 loops. The example:
```
repeat 3 times if x is pressed forward 15
repeat 3 times if y is pressed forward 15
```
Works fine.


What I Know 🔍: The parser uses the _condition_spaces_ rule found in _level5-Additions.lark_ for the failing example above, because of that it becomes a _ifs_ rule, instead of a _ifpressed_ rule. I have tried to figure out why and how to fix it but no luck... 

@Felienne, @jpelay I hope it's okay to ask some help from you (cause I'm sort of stuck)! Will post updates here if I discover something!
  • Loading branch information
ToniSkulj authored Feb 16, 2023
1 parent 6e07a48 commit 8ff7d68
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 6 deletions.
Binary file modified all_snippet_hashes.pkl
Binary file not shown.
31 changes: 27 additions & 4 deletions hedy.py
Original file line number Diff line number Diff line change
Expand Up @@ -1957,6 +1957,7 @@ def repeat(self, meta, args):
command = args[1]
# in level 7, repeats can only have 1 line as their arguments
command = sleep_after(command, False)
self.ifpressed_prefix_added = False # add ifpressed prefix again after repeat
return f"""for {var_name} in range(int({str(times)})):
{ConvertToPython.indent(command)}"""

Expand All @@ -1981,6 +1982,7 @@ def repeat(self, meta, args):
body = "\n".join(all_lines)
body = sleep_after(body)

self.ifpressed_prefix_added = False # add ifpressed prefix again after repeat
return f"for {var_name} in range(int({times})):\n{body}"

def ifs(self, meta, args):
Expand Down Expand Up @@ -2069,7 +2071,7 @@ def for_list(self, meta, args):
body = "\n".join([ConvertToPython.indent(x) for x in args[2:]])

body = sleep_after(body, True)

self.ifpressed_prefix_added = False
return f"for {times} in {args[1]}:\n{body}"


Expand All @@ -2084,6 +2086,7 @@ def for_loop(self, meta, args):
stepvar_name = self.get_fresh_var('step')
begin = self.process_token_or_tree(args[1])
end = self.process_token_or_tree(args[2])
self.ifpressed_prefix_added = False # add ifpressed prefix again after for loop
return f"""{stepvar_name} = 1 if {begin} < {end} else -1
for {iterator} in range({begin}, {end} + {stepvar_name}, {stepvar_name}):
{body}"""
Expand Down Expand Up @@ -2268,6 +2271,7 @@ def while_loop(self, meta, args):
body = "\n".join(all_lines)
body = sleep_after(body)
exceptions = self.make_catch_exception([args[0]])
self.ifpressed_prefix_added = False # add ifpressed prefix again after while loop
return exceptions + "while " + args[0] + ":\n" + body


Expand Down Expand Up @@ -2689,6 +2693,24 @@ def starts_with(command, line):
else:
return line[0:len(command)] == command

def starts_with_after_repeat(command, line):
elements_in_line = line.split()
repeat_plus_translated = ['repeat', KEYWORDS[lang].get('repeat')]
times_plus_translated = ['times', KEYWORDS[lang].get('times')]

if len(elements_in_line) > 2 and elements_in_line[0] in repeat_plus_translated and elements_in_line[2] in times_plus_translated:
line = ' '.join(elements_in_line[3:])

if lang in ALL_KEYWORD_LANGUAGES:
command_plus_translated_command = [command, KEYWORDS[lang].get(command)]
for c in command_plus_translated_command:
# starts with the keyword and next character is a space
if line[0:len(c)] == c and (len(c) == len(line) or line[len(c)] == ' '):
return True
return False
else:
return line[0:len(command)] == command

def contains(command, line):
if lang in ALL_KEYWORD_LANGUAGES:
command_plus_translated_command = [command, KEYWORDS[lang].get(command)]
Expand All @@ -2711,18 +2733,19 @@ def contains_any_of(commands, line):
if contains(c, line):
return True
return False

for i in range(len(lines) - 1):
line = lines[i]
next_line = lines[i + 1]

# if this line starts with if but does not contain an else, and the next line too is not an else.
if starts_with('if', line) and (not starts_with('else', next_line)) and (not contains('else', line)):
if (starts_with('if', line) or starts_with_after_repeat('if', line)) and (not starts_with('else', next_line)) and (not contains('else', line)):
# is this line just a condition and no other keyword (because that is no problem)
commands = ["print", "ask", "forward", "turn"]

if (
not contains('pressed', line) and contains_any_of(commands, line)
): # and this should also (TODO) check for a second is cause that too is problematic.
contains_any_of(commands, line)
): # and this should also (TODO) check for a second `is` cause that too is problematic.
# a second command, but also no else in this line -> check next line!

# no else in next line?
Expand Down
13 changes: 11 additions & 2 deletions tests/test_level/test_level_05.py
Original file line number Diff line number Diff line change
Expand Up @@ -878,12 +878,15 @@ def test_double_if_pressed(self):
if event.unicode == 'x':
print(f'first key')
break
else:
_ = 'x'
break
if event.type == pygame.KEYDOWN:
if event.unicode == 'y':
print(f'second key')
break""")

self.multi_level_tester(code=code, expected=expected, max_level=7)
self.multi_level_tester(code=code, expected=expected, max_level=7, translate=False)

def test_if_pressed_has_enter_after_pressed(self):
code = textwrap.dedent("""\
Expand Down Expand Up @@ -1223,16 +1226,22 @@ def test_if_pressed_non_latin(self):
if event.unicode == 'ض':
print(f'arabic')
break
else:
_ = 'x'
break
if event.type == pygame.KEYDOWN:
if event.unicode == 'ש':
print(f'hebrew')
break
else:
_ = 'x'
break
if event.type == pygame.KEYDOWN:
if event.unicode == 'й':
print(f'russian')
break""")

self.multi_level_tester(code=code, expected=expected, max_level=7)
self.multi_level_tester(code=code, expected=expected, max_level=7, translate=False)

#
# pressed negative tests
Expand Down
147 changes: 147 additions & 0 deletions tests/test_level/test_level_07.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,150 @@ def test_if_pressed_repeat(self):
self.single_level_tester(
code=code,
expected=expected)

def test_if_pressed_multiple(self):
code = textwrap.dedent("""\
if x is pressed print 'doe het 1 keer!'
if y is pressed print 'doe het 1 keer!'
if z is pressed print 'doe het 1 keer!'""")

expected = HedyTester.dedent("""\
while not pygame_end:
pygame.display.update()
event = pygame.event.wait()
if event.type == pygame.QUIT:
pygame_end = True
pygame.quit()
break
if event.type == pygame.KEYDOWN:
if event.unicode == 'x':
print(f'doe het 1 keer!')
break
else:
_ = 'x'
break
if event.type == pygame.KEYDOWN:
if event.unicode == 'y':
print(f'doe het 1 keer!')
break
else:
_ = 'x'
break
if event.type == pygame.KEYDOWN:
if event.unicode == 'z':
print(f'doe het 1 keer!')
break""")

self.single_level_tester(
code=code,
expected=expected,
translate=False)

def test_repeat_if_pressed_multiple(self):
code = textwrap.dedent("""\
repeat 3 times if x is pressed forward 15
repeat 3 times if y is pressed forward 15
repeat 3 times if z is pressed forward 15""")

expected = HedyTester.dedent("""\
for __i__ in range(int('3')):
while not pygame_end:
pygame.display.update()
event = pygame.event.wait()
if event.type == pygame.QUIT:
pygame_end = True
pygame.quit()
break
if event.type == pygame.KEYDOWN:
if event.unicode == 'x':
__trtl = 15
try:
__trtl = int(__trtl)
except ValueError:
raise Exception(f'While running your program the command <span class="command-highlighted">forward</span> received the value <span class="command-highlighted">{__trtl}</span> which is not allowed. Try changing the value to a number.')
t.forward(min(600, __trtl) if __trtl > 0 else max(-600, __trtl))
time.sleep(0.1)
break
else:
_ = 'x'
break
time.sleep(0.1)
for __i__ in range(int('3')):
while not pygame_end:
pygame.display.update()
event = pygame.event.wait()
if event.type == pygame.QUIT:
pygame_end = True
pygame.quit()
break
if event.type == pygame.KEYDOWN:
if event.unicode == 'y':
__trtl = 15
try:
__trtl = int(__trtl)
except ValueError:
raise Exception(f'While running your program the command <span class="command-highlighted">forward</span> received the value <span class="command-highlighted">{__trtl}</span> which is not allowed. Try changing the value to a number.')
t.forward(min(600, __trtl) if __trtl > 0 else max(-600, __trtl))
time.sleep(0.1)
break
else:
_ = 'x'
break
time.sleep(0.1)
for __i__ in range(int('3')):
while not pygame_end:
pygame.display.update()
event = pygame.event.wait()
if event.type == pygame.QUIT:
pygame_end = True
pygame.quit()
break
if event.type == pygame.KEYDOWN:
if event.unicode == 'z':
__trtl = 15
try:
__trtl = int(__trtl)
except ValueError:
raise Exception(f'While running your program the command <span class="command-highlighted">forward</span> received the value <span class="command-highlighted">{__trtl}</span> which is not allowed. Try changing the value to a number.')
t.forward(min(600, __trtl) if __trtl > 0 else max(-600, __trtl))
time.sleep(0.1)
break
time.sleep(0.1)""")

self.single_level_tester(
code=code,
expected=expected,
translate=False)

def test_repeat_if_multiple(self):
code = textwrap.dedent("""\
aan is ja
repeat 3 times if aan is ja print 'Hedy is leuk!'
repeat 3 times if aan is ja print 'Hedy is leuk!'""")

expected = HedyTester.dedent("""\
aan = 'ja'
for __i__ in range(int('3')):
if convert_numerals('Latin', aan) == convert_numerals('Latin', 'ja'):
print(f'Hedy is leuk!')
else:
_ = 'x'
time.sleep(0.1)
for __i__ in range(int('3')):
if convert_numerals('Latin', aan) == convert_numerals('Latin', 'ja'):
print(f'Hedy is leuk!')
time.sleep(0.1)""")

output = textwrap.dedent("""\
Hedy is leuk!
Hedy is leuk!
Hedy is leuk!
Hedy is leuk!
Hedy is leuk!
Hedy is leuk!""")

self.single_level_tester(
code=code,
expected=expected,
output=output,
translate=False)

0 comments on commit 8ff7d68

Please sign in to comment.