Bells - Ring the changes
Due to the way jsgame0.js takes control of the keyboard, you need to click "Pause" before pasting anything with the keyboard while the sequence is running.
Use number keys 4 through 8 to change the number of bells if you did not paste a bell sequence above.
Use the RETURN key to reset the sequence.
Use the "-" key to increase the delay between each round.
Use the "=" key to decrease the delay between each round.
I simplified the interface from the original Python code. In particular, automatic was removed. Instead a random swap is chosen at the start of each round and you can override it with the mouse.
Attribution
Mike's Pi Bakery: Ring the Changes, pages 40 - 45, by Mike Cook.
Licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported.
Original Python code
#!/usr/bin/env python3
# Pi Ring the changes - bells_play
# By Mike Cook - December 2017
import pygame, time, os, copy, random
from tkinter import filedialog
from tkinter import *
pygame.init() # initialise graphics interface
pygame.mixer.quit()
pygame.mixer.init(frequency=22050, size=-16, channels=2, buffer=512)
os.environ['SDL_VIDEO_WINDOW_POS'] = 'center'
pygame.display.set_caption("Bells - Ring the changes")
pygame.event.set_allowed(None)
pygame.event.set_allowed([pygame.KEYDOWN,pygame.QUIT,
pygame.MOUSEBUTTONDOWN])
screenWidth = 1260 ; screenHight = 482
screen = pygame.display.set_mode([screenWidth,screenHight],0,32)
textHeight=26 ; hangY = 30
font = pygame.font.Font(None, textHeight)
swingSpeed = 0.01 # animation rate
bellX = [60,180,320,460,620,790,973,1160]
backCol = (0,255,255) # background colour
trails = [(255,0,0),(255,255,0),(0,255,0),(0,0,255),
(0,0,0),(255,128,0), (255,255,255), (32,120,0)]
speed = 0.4 ; running = False ; automatic = False
random.seed() ; ringLength = 8 ; filePlay = False
def main():
global lastSequence, swapFrom, running, bellSequence
drawLables()
resetSequence()
loadResources()
print("Ring in the new - press R to ring")
print("S to stop - F to play a file - C to ring the changes")
while True:
checkForEvent()
if filePlay :
if running:
drawControls()
lastSequence = fSeq[0]
i=-1
while i < int(len(fSeq))-1 and running:
i += 1
if int(len(fSeq[i])) > 0 :
if int(fSeq[i] !=0):
bellSequence = fSeq[i]
playPeal()
drawSequence()
lastSequence = copy.deepcopy(bellSequence[:])
running = False
else:
if running:
playPeal()
lastSequence = copy.deepcopy(bellSequence[:])
if swapFrom != -1: # if we need to swap
bellSequence[swapFrom],bellSequence[swapFrom+1]=bellSequence[swapFrom+1],bellSequence[swapFrom]
swapFrom = -1 # remove swap call
drawControls()
drawSequence()
def playPeal():
global swapFrom, speed
for ring in range(0,ringLength):
showRing(ring)
swing(bellSequence[ring])
if ring ==2 and automatic and not(filePlay): # random swap
swapFrom = random.randint(0,ringLength-2)
drawControls()
pygame.display.update()
checkForEvent()
time.sleep(speed)
def setMode(mode):
global filePlay
filePlay = mode
if filePlay:
root = Tk()
root.filename = filedialog.askopenfilename(initialdir = "/home/pi",
title = "Select bell method",filetypes = (("txt files","*.txt"),
("all files","*.*")))
loadFile(root.filename)
root.withdraw()
else :
pygame.display.set_caption("Bells - Ring the changes")
resetSequence()
def loadFile(fileName):
global fSeq, ringLength
nameF = open(fileName,"r")
pygame.display.set_caption("Playing - "+fileName)
sequenceFile = nameF.readlines()
ringLength = int(len(sequenceFile[0]) / 2)
fSeq = [] ; k=-1
for i in sequenceFile:
k +=1
ns = []
for j in range(0,int(len(sequenceFile[k])),2):
if i[j:j+1] != '-' and i[j:j+1] != '\n':
n = int(i[j:j+1])-1 # to get bells 0 to 7
ns.append(n)
fSeq.append(ns)
fSeq.append(ns) # extra line at end
nameF.close()
def showRing(n): # indicate the current ring point
pygame.draw.rect(screen,backCol,(524,248,185,16),0)
drawWords("^",530+n*24,248,(0,0,0),backCol)
pygame.display.update()
def drawControls(): # draw swap radio buttons
pygame.draw.rect(screen,backCol,(0,160,screenWidth,20),0)
if filePlay:
return
for n in range(0,ringLength-1):
if n == swapFrom:
pygame.draw.rect(screen,(128,32,32),(bellX[n]+10,160,bellX[n+1]-bellX[n]-20,20),0)
drawWords("<-- Swap -->",bellX[n]+10+n*6,160,(0,0,0),(128,32,32))
else:
drawWords("<-- Swap -->",bellX[n]+10+n*6,160,(0,0,0),backCol)
pygame.draw.rect(screen,(0,0,0),(bellX[n]+10,160,bellX[n+1]-bellX[n]-20,20),1)
def drawSequence(): # display bell sequence
screen.set_clip(0,260,screenWidth,screenHight-260)
screen.scroll(-30,0)
screen.set_clip(0,0,screenWidth,screenHight)
for n in range(0,ringLength):
t = -1 ; j = 0
while t == -1:
if bellSequence[j] == lastSequence[n]:
t = j
j +=1
pygame.draw.line(screen,trails[lastSequence[n]],(screenWidth-50,screenHight-16-n*24),(screenWidth-30,screenHight-16-t*24),4)
pygame.draw.rect(screen,backCol,(530,227,179,20),0)
pygame.draw.rect(screen,backCol,(screenWidth-30,screenHight-200,16,191),0)
for n in range(0,ringLength):
drawWords(str(bellSequence[n]+1),530+n*24,227,(0,0,0),backCol) # horizontally
drawWords(str(bellSequence[n]+1),screenWidth-30,screenHight-(n+1)*24,(0,0,0),backCol) # vertically
pygame.display.update()
def drawLables():
global textHeight
textHeight = 26
pygame.draw.rect(screen,backCol,(0,0,screenWidth,screenHight),0)
for n in range(0,8):
drawWords(str(n+1),bellX[n]-4,0,(0,0,0),backCol)
textHeight = 36
drawWords("<---- Sequence ---->",532,207,(0,0,0),backCol)
def swing(bellNumber): # animated bell swing
global bellState
if bellState[bellNumber] :
for pos in range(1,11): # swing one direction
showBell(bellNumber,pos,pos-1)
time.sleep(swingSpeed)
bellState[bellNumber] = 0
else:
for pos in range(9,-1,-1): # swing the other direction
showBell(bellNumber,pos,pos+1)
time.sleep(swingSpeed)
bellState[bellNumber] = 1
samples[bellNumber].play() # make sound
def showBell(bellNumber,seqNumber,lastBell): # show one frame of the bell
cRect = bells[bellNumber][lastBell].get_rect()
cRect.move_ip((bellX[bellNumber]-plotPoints[bellNumber][lastBell][0],
hangY-plotPoints[bellNumber][lastBell][1]) )
pygame.draw.rect(screen,backCol,cRect,0) # clear last bell image
screen.blit(bells[bellNumber][seqNumber],(bellX[bellNumber]
-plotPoints[bellNumber][seqNumber][0],
hangY-plotPoints[bellNumber][seqNumber][1]))
pygame.display.update()
def drawWords(words,x,y,col,backCol) :
textSurface = pygame.Surface((14,textHeight))
textRect = textSurface.get_rect()
textRect.left = x
textRect.top = y
textSurface = font.render(words, True, col, backCol)
screen.blit(textSurface, textRect)
def loadResources():
global bells, plotPoints, bellState, samples, swapIcon
bellState = [1,1,1,1,1,1,1,1]
scale = [12.0,11.0,10.15,9.42,8.8,8.25,7.76,7.33] # size of bell
point = [(676, 63),(646, 73),(606, 73),(532, 75),(452, 71),
(380,67),(290, 71),(214, 61),(154, 57),(118, 77),(114, 75) ]
plotPoints = []
bells = []
for scaledBell in range(0,8):# get images of bells and scale them
plotPoint = []
bell = [ pygame.transform.smoothscale(pygame.image.load(
"swing/b"+str(b)+".png").convert_alpha(),(int(792.0/scale[scaledBell]),
int(792.0/scale[scaledBell]))) for b in range(0,11)]
for p in range(0,11):
p1 = int(point[p][0] / scale[scaledBell])
p2 = int(point[p][1] / scale[scaledBell])
plotPoint.append((p1,p2))
bells.append(bell)
plotPoints.append(plotPoint)
showBell(scaledBell,0,0)
samples = [pygame.mixer.Sound("sounds/"+str(pitch)+".wav")
for pitch in range(0,8)]
def resetSequence():
global bellSequence, swapFrom,lastSequence
bellSequence = [0,1,2,3,4,5,6,7]
lastSequence = [0,1,2,3,4,5,6,7]
swapFrom = -1
pygame.draw.rect(screen,backCol,(0,227,screenWidth,253),0)
drawControls()
drawSequence()
def handleMouse(pos): # look at click for swap positions
global swapFrom
if filePlay :
return
update = False
if pos[1] > 160 and pos[1] < 180: # swap click
for b in range(0,ringLength-1):
if pos[0] > bellX[b]+10 and pos[0] < bellX[b+1]+10 :
swapFrom = b
update = True
if update :
drawControls()
pygame.display.update()
def terminate(): # close down the program
pygame.mixer.quit()
pygame.quit() # close pygame
os._exit(1)
def checkForEvent(): # see if we need to quit
global speed, running,ringLength, automatic
event = pygame.event.poll()
if event.type == pygame.QUIT :
terminate()
if event.type == pygame.KEYDOWN :
if event.key == pygame.K_ESCAPE :
terminate()
if event.key == pygame.K_RETURN and not filePlay: # reset sequence
resetSequence()
if event.key > pygame.K_3 and event.key < pygame.K_9 and not filePlay:
ringLength = event.key & 0x0f # number of bells
drawControls()
drawSequence()
if event.key == pygame.K_a : # automatic swap
automatic = not(automatic)
if event.key == pygame.K_r : # run bell
running = True
if event.key == pygame.K_s : # stop bells
running = False
if event.key == pygame.K_EQUALS : # reduce speed
speed -= 0.04
if speed < .08:
speed = .08
if event.key == pygame.K_MINUS : # increase speed
speed += 0.04
if event.key == pygame.K_c : # ring changes
setMode(False)
if event.key == pygame.K_f : # play a file
setMode(True)
if event.type == pygame.MOUSEBUTTONDOWN :
handleMouse(pygame.mouse.get_pos())
# Main program logic:
if __name__ == '__main__':
main()