#!/usr/bin/env python """ Simple 3x3 Sam Loyd sliding puzzle. Written by Rich Burridge - January 2007 Version 0.1 Code derived from the chimp.py amd moveit.py example by Pete Shinners. and JavaScript code found at: http://www.squiglysplayhouse.com/Games/TileGame/Thanksgiving.html Sound samples taken from the Magicor game: http://www.pygame.org/projects/21/313/?release_id=528 """ import os, pygame import time as pytime from math import fabs from operator import mod from random import random from pygame.locals import * if not pygame.mixer: print 'Warning, sound disabled' class Tile: def __init__(self, image, row, col, missing): self.image = image self.row = row self.col = col self.missing = missing self.width = 200 self.height = 200 self.pos = self.image.get_rect().move(col * 200, row * 200) def move(self, xInc, yInc): self.pos = self.pos.move(xInc, yInc) class SlidingPuzzle: def __init__(self): self.nGridSize = 3 self.nGridLen = self.nGridSize * self.nGridSize self.nEmptyRow = 0 self.nEmptyCol = 0 self.nTurns = 0 self.t = [] def load_image(self, name): path = os.path.join('data', name) return pygame.image.load(path).convert() def load_sound(self, name): class NoneSound: def play(self): pass if not pygame.mixer or not pygame.mixer.get_init(): return NoneSound() fullname = os.path.join('data', name) try: sound = pygame.mixer.Sound(fullname) except pygame.error, message: print 'Cannot load sound:', fullname raise SystemExit, message return sound def click(self, nRow, nCol): if nRow == self.nEmptyRow and nCol == self.nEmptyCol: self.error_sound.play() return # This is the empty space! if nRow == self.nEmptyRow or nCol == self.nEmptyCol: self.moveTiles(nRow, nCol) self.nTurns += 1 if self.checkTiles() == True: self.solved_sound.play() print "You solved the puzzle in ", self.nTurns, " moves." self.nTurns = 0 else: self.error_sound.play() def moveTiles(self, nRow, nCol): if nRow == self.nEmptyRow: # Horizontal move if nCol < self.nEmptyCol: # Move right for i in range(self.nEmptyCol-1, nCol-1, -1): self.moveTile(nRow, i, 1, 0) else: # Move left for i in range(self.nEmptyCol+1, nCol+1): self.moveTile(nRow, i, -1, 0) else: # Vertical move if nRow > self.nEmptyRow: # Move up for i in range(self.nEmptyRow+1, nRow+1): self.moveTile(i, nCol, 0, -1) else: # Move down for i in range(self.nEmptyRow-1, nRow-1, -1): self.moveTile(i, nCol, 0, 1) def moveTile(self, nRow, nCol, xInc, yInc): for i in range(0, len(self.t)): if self.t[i].row == nRow and \ self.t[i].col == nCol and self.t[i].missing == False: endX = self.t[i].pos.left + (self.t[i].width * xInc) endY = self.t[i].pos.top + (self.t[i].height * yInc) self.slide_sound.play() xBack = self.t[i].pos.left yBack = self.t[i].pos.top finished = False while not finished: self.screen.blit(self.background, Rect(xBack, yBack, 200, 200)) self.t[i].move(xInc, yInc) self.screen.blit(self.t[i].image, self.t[i].pos) pygame.display.update() if self.t[i].pos.left == endX and self.t[i].pos.top == endY: finished = True self.blockhit_sound.play() self.t[i].row += yInc self.t[i].col += xInc self.nEmptyRow = nRow self.nEmptyCol = nCol break def checkTiles(self): for row in range(0, self.nGridSize): for col in range(0, self.nGridSize): n = col + (row * self.nGridSize) if self.t[n].row != row or self.t[n].col != col: return False return True def shuffle(self): nIteration = 0 nIterations = self.nGridLen * 2 while nIteration < nIterations: if nIteration % 2 == 0: nRow = int(random() * self.nGridSize) if nRow != self.nEmptyRow: self.moveTiles(nRow, self.nEmptyCol) else: nIteration -= 1 else: nCol = int(random() * self.nGridSize) if nCol != self.nEmptyCol: self.moveTiles(self.nEmptyRow, nCol) else: nIteration -= 1 nIteration += 1 self.nTurns = 0 def main(self): tiles = [("tile1.bmp", 0, 0), ("tile2.bmp", 0, 1), ("tile3.bmp", 0, 2), ("tile4.bmp", 1, 0), ("tile5.bmp", 1, 1), ("tile6.bmp", 1, 2), ("tile7.bmp", 2, 0), ("tile8.bmp", 2, 1), ("tile9.bmp", 2, 2)] pygame.init() self.screen = pygame.display.set_mode((600, 600)) pygame.display.set_caption('Duncan Puzzle') for name, row, col in tiles: self.t.append(Tile(self.load_image(name), row, col, False)) self.t[0].missing = True self.background = pygame.Surface((200, 200)) self.background = self.background.convert() self.background.fill((40, 40, 40)) self.screen.blit(self.background, (0, 0)) pygame.display.flip() clock = pygame.time.Clock() self.blockhit_sound = self.load_sound('blockhit.wav') self.slide_sound = self.load_sound('slide.wav') self.solved_sound = self.load_sound('solved.wav') self.error_sound = self.load_sound('error.wav') self.screen.blit(self.background, (0, 0)) for i in range(1, len(self.t)): if self.t[i].image: self.screen.blit(self.t[i].image, self.t[i].pos) pygame.display.update() while 1: event = pygame.event.wait() if event.type == QUIT: return elif event.type == KEYDOWN: if event.key == K_s: self.shuffle() if event.key == K_DOWN: self.click(self.nEmptyRow-1, self.nEmptyCol) if event.key == K_UP: self.click(self.nEmptyRow+1, self.nEmptyCol) if event.key == K_LEFT: self.click(self.nEmptyRow, self.nEmptyCol+1) if event.key == K_RIGHT: self.click(self.nEmptyRow, self.nEmptyCol-1) if event.key == K_ESCAPE or event.key == K_q: return elif event.type == MOUSEBUTTONDOWN: (x, y) = pygame.mouse.get_pos() tileClicked = [tile for tile in self.t \ if tile.pos.collidepoint(x, y) and \ tile.missing == False] if tileClicked: self.click(tileClicked[0].row, tileClicked[0].col) if __name__ == '__main__': sp = SlidingPuzzle() sp.main()