Android's Lock Screen Pattern In Pygame
Implementing Android's lock pattern in Pygame is an interesting attempt at mocking great Uis.
Steps
Here's a rough outline of what we need to think about:
- Display buttons
- Record user choice
- Draw patterns based on user choice
- Have a pattern length
- Checking the right password
- Reset choice
I recommend you attempt to implement it in pygame or p5py then come back to read the article.
The skeleton
We are using hooman in this tutorial which implements p5js api in Pygame.
python -m pip install hooman
Here's a basic skeleton which displays an empty window
from hooman import Hooman
import pygame
pen = Hooman(500, 500)
while pen.is_running:
pen.background(255)
pen.flip_display()
pen.event_loop()
Display buttons
Let's implement our pattern buttons as mere circles
class LockButton:
def __init__(self, x, y, pen, num=0):
self.x = x
self.y = y
self.pen = pen
self.num = num
def coords(self):
return (self.x, self.y)
def draw(self):
pen = self.pen
pen.fill(0)
pen.ellipse(self.x, self.y, 20, 20)
def update(self):
pass
def run(self):
self.update()
self.draw()
We then initialise them in a list
# --- initialise buttons ---
button_id = 1
for x in range(5):
for y in range(5):
buttons.append(LockButton(x * 50 + 10, y * 50 + 10, pen, num=button_id))
button_id += 1
Then draw them from the list
while pen.is_running:
# ...
# --- display lock buttons ---
for b in buttons:
b.run()
# ...
Record user choice
To record user choice, we record the coordinates of the selected buttons so as not to draw a line on it again and, we record the button id so that we can easily compare it to our password pattern
from hooman import Hooman
from hooman.formula import distance
import pygame
pen = Hooman(500, 500)
attempt_button_coords = [] # coords for lock buttons in user pattern
attempt_button_ids = [] # ids of lock buttons in user pattern
buttons = [] # list of buttons to display
class LockButton:
# ...
def update(self):
pen = self.pen
global attempt_button_coords, attempt_button_ids
if pen.pygame.mouse.get_pressed()[0]: # if mouse pressed
mouse_coords = (pen.mouseX(), pen.mouseY())
if distance(self.coords(), mouse_coords) <= 20:
if self.coords() not in attempt_button_coords:
attempt_button_ids.append(self.num)
attempt_button_coords.append(self.coords())
# ...
Draw patterns based on user choice
while pen.is_running:
# ...
# --- draw lock pattern ---
pen.fill(0)
for i, c in enumerate(attempt_button_coords):
try:
pen.stroke_size(5)
pen.stroke(pen.color["green"])
pen.line(
c[0] + 10,
c[1] + 10,
attempt_button_coords[i + 1][0] + 10,
attempt_button_coords[i + 1][1] + 10,
)
except Exception as e:
pass
# --- draw last part of pattern ---
try:
pen.line(
attempt_button_coords[-1][0] + 10,
attempt_button_coords[-1][1] + 10,
pen.mouseX(),
pen.mouseY(),
)
except:
pass
But, we have a problem: The patterns go on indefinitely.
Have a pattern length
# inside loop
# --- if max pattern length ---
if len(attempt_button_coords) >= 7:
attempt_button_coords = []
attempt_button_ids = []
Check for the right password
display_win = False
pattern = [1, 2, 3, 4, 5, 6, 7] # ids of buttons of password
# ...
# in loop
# --- if max pattern length ---
if len(attempt_button_coords) >= 7:
if attempt_button_ids == pattern: # this
display_win = True
# ...
# --- right password found ---
if display_win:
pen.fill((200, 200, 50))
pen.font_size(30)
pen.text("Right password!", 300, 50)
Adding the reset button
To enable us to retry a pattern, we add a reset button which empties the pattern list. We use a hooman button.
# ...
from hooman.ui import Button
# --- reset button ---
def reset_clicked(this):
global attempt_button_coords, attempt_button_ids, display_win
attempt_button_coords = []
attempt_button_ids = []
display_win = False
reset_button_styles = {
"hover_background_color": (200, 200, 200),
"font_size": 10,
"background_color": (210, 210, 210),
"on_click": reset_clicked,
"curve": 1,
}
reset_button = Button(250, 10, 150, 20, "Reset", reset_button_styles)
while pen.is_running:
# ...
reset_button.update()
Complete code
from hooman import Hooman
from hooman.formula import distance
from hooman.ui import Button
import pygame
pen = Hooman(500, 500)
attempt_button_coords = [] # coords for lock buttons in user pattern
attempt_button_ids = [] # ids of lock buttons in user pattern
buttons = [] # list of buttons to display
display_win = False
pattern = [1, 2, 3, 4, 5, 6, 7] # ids of password
# --- reset button ---
def reset_clicked(this):
global attempt_button_coords, attempt_button_ids, display_win
attempt_button_coords = []
attempt_button_ids = []
display_win = False
reset_button_styles = {
"hover_background_color": (200, 200, 200),
"font_size": 10,
"background_color": (210, 210, 210),
"on_click": reset_clicked,
"curve": 1,
}
reset_button = Button(250, 10, 150, 20, "Reset", reset_button_styles)
class LockButton:
def __init__(self, x, y, pen, num=0):
self.x = x
self.y = y
self.pen = pen
self.num = num
def coords(self):
return (self.x, self.y)
def draw(self):
pen = self.pen
pen.fill(0)
pen.ellipse(self.x, self.y, 20, 20)
def update(self):
pen = self.pen
global attempt_button_coords, attempt_button_ids
if pen.pygame.mouse.get_pressed()[0]:
mouse_coords = (pen.mouseX(), pen.mouseY())
if distance(self.coords(), mouse_coords) <= 20:
if self.coords() not in attempt_button_coords:
attempt_button_ids.append(self.num)
attempt_button_coords.append(self.coords())
def run(self):
self.update()
self.draw()
# --- initialise buttons ---
button_id = 1
for x in range(5):
for y in range(5):
buttons.append(LockButton(x * 50 + 10, y * 50 + 10, pen, num=button_id))
button_id += 1
while pen.is_running:
pen.background(255)
# --- draw lock pattern ---
pen.fill(0)
for i, c in enumerate(attempt_button_coords):
try:
pen.stroke_size(5)
pen.stroke(pen.color["green"])
pen.line(
c[0] + 10,
c[1] + 10,
attempt_button_coords[i + 1][0] + 10,
attempt_button_coords[i + 1][1] + 10,
)
except Exception as e:
pass
# --- draw last part of pattern ---
try:
pen.line(
attempt_button_coords[-1][0] + 10,
attempt_button_coords[-1][1] + 10,
pen.mouseX(),
pen.mouseY(),
)
except:
pass
# --- display lock buttons ---
for b in buttons:
b.run()
# --- if max pattern length ---
if len(attempt_button_coords) >= 7:
if attempt_button_ids == pattern:
display_win = True
attempt_button_coords = []
attempt_button_ids = []
# --- right password found ---
if display_win:
pen.fill((200, 200, 50))
pen.font_size(30)
pen.text("Right password!", 300, 50)
reset_button.update()
pen.flip_display()
pen.event_loop()