This article is an example of a 15-Puzzle game using the ml4m board with the results shown in Figure 1. It is a game that allows players to practice their strategic thinking skills, looking ahead to plan their shifting numbers. In addition to being in the form of numbers, it can also be changed from numbers to images, that is, transformed into an image and divided into 16 parts, and then allow the player to move the image to make it the same as the original. Also in the example, a buzzer is used to generate a beep sound using an 8-bit DAC of an esp32 microcontroller, as well as programming in Python on MicroPython.
15 Puzzle
The 15 puzzle game is a 4×4 grid game as shown in the first Figure, where each square contains the numbers 1 to 15 and one space. In this article, the numbers 1 to 9 and the letter A, B, C, D, E and F represent the numbers 10,11,12,13,14 and 15 because we want to save the display space because the display of the ml4m board is 128×64 dots.
ตำแหน่งค่า (แถว,คอลัมน์) ของข้อมูลแต่ละตำแหน่งดังนี้
Therefore, we use a list variable to store data items within a table called randPuzzle and create a posPuzzle variable to store values (rows, columns) of each data location as follows.
## store 4x4 random value
randPuzzle = []
## store value in x,y of randPuzzle ie. position 3 in randPuzzle is posPuzzle[3]
## (3,0) mean first 3 column for position checking
posPuzzle = (
(0,0),(1,0),(2,0),(3,0),
(0,1),(1,1),(2,1),(3,1),
(0,2),(1,2),(2,2),(3,2),
(0,3),(1,3),(2,3),(3,3))
Rules
The rules of the game play are defined as follows.
- The lever is used to move left, right, up or down.
- swA to start a new game with randomize a new table
- swB to exit the game
- to win the game, the numbers must be arranged as follows.
1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 |
9 | A | B | C |
D | E | F |
Random numbers
Random numbers are drawn from a list of values stored in a tmp variable. The algorithm is as follows.
- Create a list of 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E and F.
- Empty the randPuzzle variable.
- Do random 15 times.
- Randomly select data from tmp.
- Bring the randomly selected data to the randPuzzle.
- Remove random data from tmp.
- Take the last value stored in tmp and store it in randPuzzle.
- Remove the last value from tmp.
The code for randomly placing numbers in the table is as follows.
def randomTable():
global randPuzzle
tmp = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"]
randPuzzle = []
for i in range(15):
data = random.choice(tmp) # random
randPuzzle.append(data) # stored in table
tmp.remove(data) # take chosen value
randPuzzle.append(tmp[0]) # move the last to table
tmp.pop() # remove
tmp=[] # clear list
gc.collect()
movement
Movement is based on moving the joystick module’s lever. By considering the shift from the position of the space as the following conditions.
- If the lever is moved to the right and the right of the space has data, it will swap that data with the space.
- If the lever is moved to the left and the left of the space has data, it will swap that data with the space.
- If the lever is moved to the top and the top of the space has data, it will swap that data with the space.
- If the lever is moved to the bottom and the bottom of the space has data, it will swap that data with the space.
The program code of the move section is as follows.
def moveControl():
global randPuzzle
while True:
# find the position of "0"
(xp,yp,p) = find0()
## draw
drawTable()
## check winner
if (isWin()):
youWin()
break
## input
(a,d,w,s,n1,n2) = getInput()
if (n1):
randomTable()
(xp,yp,p) = find0()
youLost()
if (n2):
break
if (a and (not d) and (not w) and (not s)):
#swap value
if (xp < 3):
pos = yp*numTiles+(xp+1)
randPuzzle[p] = randPuzzle[pos]
randPuzzle[pos] = "0"
beep()
if ((not a) and (d) and (not w) and (not s)):
# swap value
if (xp > 0):
pos = yp*numTiles+(xp-1)
randPuzzle[p] = randPuzzle[pos]
randPuzzle[pos] = "0"
beep()
if ((not a) and (not d) and (w) and (not s)):
# swap value
if (yp < 3):
pos = (yp+1)*numTiles+xp
randPuzzle[p] = randPuzzle[pos]
randPuzzle[pos] = "0"
beep()
if ((not a) and (not d) and (not w) and (s)):
# swap value
if (yp >0):
pos = (yp-1)*numTiles+xp
randPuzzle[p] = randPuzzle[pos]
randPuzzle[pos] = "0"
beep()
time.sleep_ms(100)
Win and lost
Losing is caused by the user pressing the swA to randomize the position and the screen will move to the right. After that, the message “u Lost” is displayed as shown in the Figure 2.
def youLost():
for i in range(32):
oled.scroll(1,0)
oled.show()
oled.text("You",10,20)
oled.text("Lost",9,30)
oled.text("Lost",10,30)
oled.text("Lost",11,30)
oled.show()
for i in range(10):
oled.invert(False)
time.sleep_ms(50)
oled.invert(True)
time.sleep_ms(50)
oled.invert(False)
time.sleep_ms(2000)
Winning is done by checking if the value stored in the randPuzzle variable matches the sort by the winPuzzle variable. If yes, then the player wins the game, otherwise, it returns False.
def isWin():
win = False
winPuzzle = ['1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','0']
if (randPuzzle == winPuzzle):
win = True
return win
The program code for the “You Win” display is as follows:
def youWin():
for i in range(32):
oled.scroll(-1,0)
oled.show()
oled.text("You",80,20)
oled.text("Win",79,30)
oled.text("Win",80,30)
oled.text("Win",81,30)
oled.show()
for i in range(10):
oled.invert(False)
time.sleep_ms(50)
oled.invert(True)
time.sleep_ms(50)
oled.invert(False)
time.sleep_ms(2000)
Code
From the part of the program mentioned above, it can be compiled into the program code as follows.
#######################################################
### 15-Puzzle
### board: ML4M
### (C) 2021, JarutEx
### https://www.jarutex.com
#######################################################
from machine import Pin,I2C,ADC, DAC
import math
import machine
import gc
import ssd1306
import random
import time
import sys
#######################################################
## System setting
gc.enable()
gc.collect()
machine.freq(240000000)
## store value in 4x4 table
randPuzzle = []
posPuzzle = (
(0,0),(1,0),(2,0),(3,0),
(0,1),(1,1),(2,1),(3,1),
(0,2),(1,2),(2,2),(3,2),
(0,3),(1,3),(2,3),(3,3))
#######################################################
## OLED
sclPin = Pin(22)
sdaPin = Pin(21)
spkPin = DAC(Pin(25, Pin.OUT))
i2c = I2C(0,scl=sclPin, sda=sdaPin, freq=400000)
oled = ssd1306.SSD1306_I2C(128,64,i2c)
oled.poweron()
oled.contrast(255)
oled.init_display()
oled.fill(0)
oled.show()
## constant
blockHeight = const(16)
blockWidth = const(16)
screenWidth = const(128)
screenHeight = const(64)
numTiles = const(4)
## Game pad
swA = Pin(32, Pin.IN)
swB = Pin(33, Pin.IN)
swC = Pin(35, Pin.IN)
swD = Pin(34, Pin.IN)
swF = Pin(26, Pin.IN) # select
swE = Pin(27, Pin.IN) # start
jst = (ADC(Pin(39)), ADC(Pin(36))) # X,Y
jst[0].width( ADC.WIDTH_12BIT ) # 12bit
jst[0].atten( ADC.ATTN_11DB ) # 3.3V
jst[1].width( ADC.WIDTH_12BIT ) # 12bit
jst[1].atten( ADC.ATTN_11DB ) # 3.3V
#######################################################
## drawTable()
## display table
#######################################################
def drawTable():
tileSize = screenHeight//(numTiles+1)
tableWidth = tileSize*numTiles
tableHeight = tileSize*numTiles
left = (screenWidth-tableWidth)//2
top = (screenHeight-tableHeight)//2
oled.fill(0)
for r in range(5):
oled.hline(left,top+r*tileSize,tableWidth,1)
for c in range(5):
oled.vline(left+c*tileSize,top,tableHeight,1)
i = 0
for r in range(4):
for c in range(4):
if (randPuzzle[i] != "0"):
oled.text(randPuzzle[i],left+c*tileSize+2,top+r*tileSize+2,1)
i += 1
oled.show()
#######################################################
## randomTable()
#######################################################
def beep():
spkPin.write(255)
time.sleep_ms(20)
spkPin.write(0)
#######################################################
## randomTable()
## random new value
#######################################################
def randomTable():
global randPuzzle
tmp = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"]
randPuzzle = []
for i in range(15):
data = random.choice(tmp) # choose
randPuzzle.append(data) # stored in table
tmp.remove(data) # remove
randPuzzle.append(tmp[0]) # move the last to table
tmp.pop() # remove
tmp=[] # clear list
gc.collect()
#######################################################
## getInput()
## input from Joystick and button switch
#######################################################
def getInput():
left = False
right = False
up = False
down = False
button1 = False
button2 = False
#### Joystick
jx = 4095-jst[0].read()
jy = jst[1].read()
if (jx < 1200):
left = True
elif (jx > 3000):
right = True
if (jy < 1200):
up = True
elif (jy > 3000):
down = True
# switch
a = swA.value()
b = swB.value()
if (a):
t0 = time.ticks_ms()
time.sleep_ms(25)
a2 = swA.value()
if (a == a2):
button1 = True
if (b):
t0 = time.ticks_ms()
time.sleep_ms(25)
b2 = swB.value()
if (b == b2):
button2 = True
return (left,right,up,down,button1, button2)
#######################################################
## isWin(p)
## ตรวจสอบการชนะเกม
#######################################################
def isWin():
win = False
winPuzzle = ['1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','0']
if (randPuzzle == winPuzzle):
win = True
return win
#######################################################
## find0()
#######################################################
def find0():
pos = randPuzzle.index("0")
return (posPuzzle[pos][0],posPuzzle[pos][1],pos)
#######################################################
## youWin()
#######################################################
def youWin():
for i in range(32):
oled.scroll(-1,0)
oled.show()
oled.text("You",80,20)
oled.text("Win",79,30)
oled.text("Win",80,30)
oled.text("Win",81,30)
oled.show()
for i in range(10):
oled.invert(False)
time.sleep_ms(50)
oled.invert(True)
time.sleep_ms(50)
oled.invert(False)
time.sleep_ms(2000)
#######################################################
## youLost()
#######################################################
def youLost():
for i in range(32):
oled.scroll(1,0)
oled.show()
oled.text("You",10,20)
oled.text("Lost",9,30)
oled.text("Lost",10,30)
oled.text("Lost",11,30)
oled.show()
for i in range(10):
oled.invert(False)
time.sleep_ms(50)
oled.invert(True)
time.sleep_ms(50)
oled.invert(False)
time.sleep_ms(2000)
#######################################################
## moveControl()
#######################################################
def moveControl():
global randPuzzle
while True:
# find the position of "0"
(xp,yp,p) = find0()
## draw
drawTable()
## winner checking
if (isWin()):
youWin()
break
## input
(a,d,w,s,n1,n2) = getInput()
if (n1):
randomTable()
(xp,yp,p) = find0()
youLost()
if (n2):
break
if (a and (not d) and (not w) and (not s)):
#swap
if (xp < 3):
pos = yp*numTiles+(xp+1)
randPuzzle[p] = randPuzzle[pos]
randPuzzle[pos] = "0"
beep()
if ((not a) and (d) and (not w) and (not s)):
# swap
if (xp > 0):
pos = yp*numTiles+(xp-1)
randPuzzle[p] = randPuzzle[pos]
randPuzzle[pos] = "0"
beep()
if ((not a) and (not d) and (w) and (not s)):
# swap
if (yp < 3):
pos = (yp+1)*numTiles+xp
randPuzzle[p] = randPuzzle[pos]
randPuzzle[pos] = "0"
beep()
if ((not a) and (not d) and (not w) and (s)):
# swap
if (yp >0):
pos = (yp-1)*numTiles+xp
randPuzzle[p] = randPuzzle[pos]
randPuzzle[pos] = "0"
beep()
time.sleep_ms(100)
#######################################################
### Main program
#######################################################
beep()
randomTable()
moveControl()
oled.poweroff()
beep()
An example of playing is as follows:
Conclusion
In this example, game programming is a type of user interaction method. How the game works is based on the idea of the programmer, not necessarily writing the same way as us. Plus, with the benefit of writing in Python, the code can be easily adapted for use on other platforms. More So we like to write in Python to create code to test ideas. After that, we can improve the performance or convert the code to a language that is faster or suitable for the platform you are using. So let’s continue to have fun with programming.
If you want to talk with us, feel free to leave commnets beloW!!
(C) 2020-2021, BY Jarut Busarathid and Danai Jedsadathitiukl
Updated 2021-12-02