150+行Python代碼實現帶界面的數獨遊戲

150行代碼實現圖形化數獨遊戲

Github地址,歡迎各位大佬們fork、star啥的,感謝;html

今天閒着沒事幹,之前作過html+js版的數獨,此次作個python版本的,界面由pygame完成,數獨生成由遞歸算法實現,由shuffle保證每次遊戲都是不同的狀況,have fun;python

功能列表:git

  • 圖形化的數獨遊戲;
  • python實現,依賴pygame庫;
  • 隨機生成遊戲,每次運行都不同;
  • 數字填入後的正確性判斷以及顏色提示;
  • 顯示剩餘需填入的空格,已經操做的次數;
  • 難度可選,經過修改須要填入的空的數量;

遊戲界面

初始界面

過程當中界面

運行方式

python main.py 15

這裏的15表示須要填入的空格數量爲15,理論上這個值越大,難度就越高,你們能夠隨機調整,或者設置容易、簡單、困難、地獄等對應不一樣的值便可,很方便修改;github

程序分析

界面部分

這部分很簡單的經過pygame來實現,主要使用了其中的主循環、鼠標鍵盤監聽、畫矩形線條、字體、顏色控制等,理解起來很容易,對於這部分不太熟悉的同窗,這樣理解就好:pygame的主循環中一方面負責接收用戶輸入,通常就是鼠標和鍵盤,另外一方面負責實時更新界面顯示內容算法

對於界面上各部份內容的繪製的函數封裝

# 繪製背景部分,這裏就是9*9的九宮格
def draw_background():
    # white background
    screen.fill(COLORS['white'])

    # draw game board
    pygame.draw.rect(screen,COLORS['black'],(0,0,300,900),5)
    pygame.draw.rect(screen,COLORS['black'],(300,0,300,900),5)
    pygame.draw.rect(screen,COLORS['black'],(600,0,300,900),5)

    pygame.draw.rect(screen,COLORS['black'],(0,0,900,300),5)
    pygame.draw.rect(screen,COLORS['black'],(0,300,900,300),5)
    pygame.draw.rect(screen,COLORS['black'],(0,600,900,300),5)

# 將用戶選中的各自背景改成藍色塊表示選中
def draw_choose():
    pygame.draw.rect(screen,COLORS['blue'],(cur_j*100+5,cur_i*100+5,100-10,100-10),0)

# 繪製九宮格中的數字,包括原本就有的,以及用戶填入的,原本就在的用灰色,用戶填入的如何合法則爲綠色,不然爲紅色,是一種提示
def draw_number():
    for i in range(len(MATRIX)):
        for j in range(len(MATRIX[0])):
            _color = check_color(MATRIX,i,j) if (i,j) in BLANK_IJ else COLORS['gray']
            txt = font80.render(str(MATRIX[i][j] if MATRIX[i][j] not in [0,'0'] else ''),True,_color)
            x,y = j*100+30,i*100+10
            screen.blit(txt,(x,y))

# 繪製最下方的當前空格子數量以及用戶的操做數量
def draw_context():
    txt = font100.render('Blank:'+str(cur_blank_size)+'   Change:'+str(cur_change_size),True,COLORS['black'])
    x,y = 10,900
    screen.blit(txt,(x,y))

主循環中對上述函數的調用以及鼠標鍵盤事件處理

# 主循環,負責監聽鼠標鍵盤時間,以及刷新界面內容,以及檢查是否贏得了遊戲
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
            break
        elif event.type == pygame.MOUSEBUTTONDOWN:
            cur_j,cur_i = int(event.pos[0]/100),int(event.pos[1]/100)
        elif event.type == event.type == pygame.KEYUP:
            if chr(event.key) in ['1','2','3','4','5','6','7','8','9'] and (cur_i,cur_j) in BLANK_IJ:
                MATRIX[cur_i][cur_j] = int(chr(event.key))
                cur_blank_size = sum([1 if col==0 or col=='0' else 0 for row in MATRIX for col in row])
                cur_change_size +=1
    # background
    draw_background()
    # choose item
    draw_choose()
    # numbers
    draw_number()
    # point
    draw_context()
    # flip
    pygame.display.flip()

    # check win or not
    if check_win(MATRIX_ANSWER,MATRIX):
        print('You win, smarty ass!!!')
        break

pygame.quit()

生成表示數獨的二維數組

相對於界面部分,這部分在邏輯上要難一些,思路以遞歸爲核心,輔以隨機性,獲得一個每次生成都不一致的數獨遊戲,生成思路簡單描述以下:數組

  1. 遍歷每一個空格,填入目前爲止合法的數字;
  2. 若是有數字能夠填入,則繼續向下一個空格;
  3. 若是沒有數字能夠填入,表示以前的數字有問題,則結束遞歸;
  4. 當遞歸到最後一個格子的下一個時,表示已經生成完畢,返回便可;
  5. 這個過程當中對1~9這九個數字的遍歷數字會通過shuffle處理,保證隨機性而不是每次都獲得同一個合法的數獨數組;

生成過程代碼

遞歸的一個優點是一般代碼都很短,固然閱讀性不強,歡迎大佬們改成循環;app

def shuffle_number(_list):
    random.shuffle(_list)
    return _list

def check(matrix,i,j,number):
    if number in matrix[i]:
        return False
    if number in [row[j] for row in matrix]:
        return False
    group_i,group_j = int(i/3),int(j/3)
    if number in [matrix[i][j] for i in range(group_i*3,(group_i+1)*3) for j in range(group_j*3,(group_j+1)*3)]:
        return False
    return True

def build_game(matrix,i,j,number):
    if i>8 or j>8:
        return matrix
    if check(matrix,i,j,number):
        _matrix = [[col for col in row] for row in matrix]
        _matrix[i][j] = number
        next_i,next_j = (i+1,0) if j==8 else (i,j+1)
        for _number in shuffle_number(number_list):
            __matrix = build_game(_matrix,next_i,next_j,_number)
            if __matrix and sum([sum(row) for row in __matrix])==(sum(range(1,10))*9):
                return __matrix
    return None

隨機覆蓋數獨數組中的N個位置

  • matrix_all表示整個數獨數組
  • matrix_blank表示部分被替換爲0的用於顯示的數組
  • blank_ij表示被覆蓋位置的i和j
def give_me_a_game(blank_size=9):
    matrix_all = build_game(matrix,0,0,random.choice(number_list))
    set_ij = set()
    while len(list(set_ij))<blank_size:
        set_ij.add(str(random.choice([0,1,2,3,4,5,6,7,8]))+','+str(random.choice([0,1,2,3,4,5,6,7,8])))
    matrix_blank = [[col for col in row] for row in matrix_all]
    blank_ij = []
    for ij in list(set_ij):
        i,j = int(ij.split(',')[0]),int(ij.split(',')[1])
        blank_ij.append((i,j))
        matrix_blank[i][j] = 0
    return matrix_all,matrix_blank,blank_ij

最後附上所有代碼

你們也能夠直接從個人Github倉庫fork下來直接運行;dom

main.py:主流程+界面+執行

import sys

import pygame
from pygame.color import THECOLORS as COLORS

from build import print_matrix,give_me_a_game,check

def draw_background():
    # white background
    screen.fill(COLORS['white'])

    # draw game board
    pygame.draw.rect(screen,COLORS['black'],(0,0,300,900),5)
    pygame.draw.rect(screen,COLORS['black'],(300,0,300,900),5)
    pygame.draw.rect(screen,COLORS['black'],(600,0,300,900),5)

    pygame.draw.rect(screen,COLORS['black'],(0,0,900,300),5)
    pygame.draw.rect(screen,COLORS['black'],(0,300,900,300),5)
    pygame.draw.rect(screen,COLORS['black'],(0,600,900,300),5)

def draw_choose():
    pygame.draw.rect(screen,COLORS['blue'],(cur_j*100+5,cur_i*100+5,100-10,100-10),0)

def check_win(matrix_all,matrix):
    if matrix_all == matrix:
        return True
    return False

def check_color(matrix,i,j):
    _matrix = [[col for col in row]for row in matrix]
    _matrix[i][j] = 0
    if check(_matrix,i,j,matrix[i][j]):
        return COLORS['green']
    return COLORS['red']

def draw_number():
    for i in range(len(MATRIX)):
        for j in range(len(MATRIX[0])):
            _color = check_color(MATRIX,i,j) if (i,j) in BLANK_IJ else COLORS['gray']
            txt = font80.render(str(MATRIX[i][j] if MATRIX[i][j] not in [0,'0'] else ''),True,_color)
            x,y = j*100+30,i*100+10
            screen.blit(txt,(x,y))

def draw_context():
    txt = font100.render('Blank:'+str(cur_blank_size)+'   Change:'+str(cur_change_size),True,COLORS['black'])
    x,y = 10,900
    screen.blit(txt,(x,y))

if __name__ == "__main__":
    # init pygame
    pygame.init()
    
    # contant
    SIZE = [900,1000]
    font80 = pygame.font.SysFont('Times', 80)
    font100 = pygame.font.SysFont('Times', 90)
    
    # create screen 500*500
    screen = pygame.display.set_mode(SIZE)
    
    # variable parameter
    cur_i, cur_j = 0,0
    cur_blank_size = int(sys.argv[1])
    cur_change_size = 0
    
    # matrix abount
    MATRIX_ANSWER,MATRIX,BLANK_IJ = give_me_a_game(blank_size=cur_blank_size)
    print(BLANK_IJ)
    print_matrix(MATRIX)
    
    # main loop
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
                break
            elif event.type == pygame.MOUSEBUTTONDOWN:
                cur_j,cur_i = int(event.pos[0]/100),int(event.pos[1]/100)
            elif event.type == event.type == pygame.KEYUP:
                if chr(event.key) in ['1','2','3','4','5','6','7','8','9'] and (cur_i,cur_j) in BLANK_IJ:
                    MATRIX[cur_i][cur_j] = int(chr(event.key))
                    cur_blank_size = sum([1 if col==0 or col=='0' else 0 for row in MATRIX for col in row])
                    cur_change_size +=1
        # background
        draw_background()
        # choose item
        draw_choose()
        # numbers
        draw_number()
        # point
        draw_context()
        # flip
        pygame.display.flip()
    
        # check win or not
        if check_win(MATRIX_ANSWER,MATRIX):
            print('You win, smarty ass!!!')
            break
    
    pygame.quit()

build.py:生成數獨數組部分

import random

def print_matrix(matrix):
    print('—'*19)
    for row in matrix:
        print('|'+' '.join([str(col) for col in row])+'|')
    print('—'*19)

def shuffle_number(_list):
    random.shuffle(_list)
    return _list

def check(matrix,i,j,number):
    if number in matrix[i]:
        return False
    if number in [row[j] for row in matrix]:
        return False
    group_i,group_j = int(i/3),int(j/3)
    if number in [matrix[i][j] for i in range(group_i*3,(group_i+1)*3) for j in range(group_j*3,(group_j+1)*3)]:
        return False
    return True

def build_game(matrix,i,j,number):
    if i>8 or j>8:
        return matrix
    if check(matrix,i,j,number):
        _matrix = [[col for col in row] for row in matrix]
        _matrix[i][j] = number
        next_i,next_j = (i+1,0) if j==8 else (i,j+1)
        for _number in shuffle_number(number_list):
            #_matrixs.append(build_game(_matrix,next_i,next_j,_number))
            __matrix = build_game(_matrix,next_i,next_j,_number)
            if __matrix and sum([sum(row) for row in __matrix])==(sum(range(1,10))*9):
                return __matrix
    #return _matrixs
    return None

def give_me_a_game(blank_size=9):
    matrix_all = build_game(matrix,0,0,random.choice(number_list))
    set_ij = set()
    while len(list(set_ij))<blank_size:
        set_ij.add(str(random.choice([0,1,2,3,4,5,6,7,8]))+','+str(random.choice([0,1,2,3,4,5,6,7,8])))
    matrix_blank = [[col for col in row] for row in matrix_all]
    blank_ij = []
    for ij in list(set_ij):
        i,j = int(ij.split(',')[0]),int(ij.split(',')[1])
        blank_ij.append((i,j))
        matrix_blank[i][j] = 0
    return matrix_all,matrix_blank,blank_ij

number_list = [1,2,3,4,5,6,7,8,9]
matrix = [([0]*9) for i in range(9)]
if __name__ == "__main__":
    print_matrix(build_game(matrix,0,0,random.choice(number_list)))

總結

若是刻意減小代碼的話,實際能夠控制在100行之內,不過也沒有這個必要,不過這也充分展現了python的強大,在很短的時間和空間上完成一些看似複雜的工做,這個例子供一些同窗上手python我的以爲仍是不錯的,沒有太複雜的用法,對界面開發有一點點了解,對遞歸有一些理解基本就能徹底掌握這份代碼,但願你們玩的開心,挑戰一下50個空格唄,哈哈,反正我沒經過,太難了。。。。機器學習

最後

你們能夠到個人Github上看看有沒有其餘須要的東西,目前主要是本身作的機器學習項目、Python各類腳本工具、有意思的小項目以及Follow的大佬、Fork的項目等:
https://github.com/NemoHoHaloAi函數

相關文章
相關標籤/搜索