用python從0開始寫2048小遊戲
最近想自己寫個小遊戲練練手,研究了一下也就2048和俄羅斯方塊最簡單了,所以就拿2048開刀了。
研究了一下2048對程式語言的要求,挺低的,就下面2點要求:
1、可以繪製介面,可以繪製圖案和顯示文字
2、可以捕捉使用者輸入(上下左右按鍵,或者對應的滑動)
很多語言都可以滿足這兩點要求,剛好最近寫了幾個python指令碼,就順便學習一下python吧。
調研了一下,開發程式一般使用wxPython包,那就它了。
wxPython基礎知識學習
第一步,先學習寫一個最簡單的wxPython應用程式:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import wx
app = wx.App()
frame = wx.Frame(None, -1, "Hello World")
frame.Show()
app.MainLoop()
這個程式能看出一個wxPython程式至少要實現這幾點
1、匯入wx包
2、例項化一個App
3、例項話一個Frame
4、顯示這個Frame
5、app進入主迴圈
這個應用只彈出一個標題為Hello World的空介面,看起來沒有任何擴充套件的功能。
然後擴充套件一下這個最簡單的應用,學習一下wxPython的用法
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import wx
class MyFrame(wx.Frame):
def __init__(self, title):
super(MyFrame, self).__init__(None, title=title, size=(250, 250))
self.Bind(wx.EVT_PAINT, self.on_paint)
self.Bind(wx.EVT_KEY_DOWN, self.on_key)
self.Centre()
self.Show()
def on_paint(self, event) :
dc = wx.PaintDC(self)
dc.SetBackground(wx.Brush(wx.LIGHT_GREY))
dc.Clear()
dc.DrawRoundedRectangle(30, 30, 100, 100, 2)
dc.DrawLine(25, 150, 190, 150)
dc.DrawText("hello world", 35, 160)
def on_key(self, event):
key_code = event.GetKeyCode()
if key_code == wx.WXK_UP:
print("UP")
elif key_code == wx.WXK_DOWN:
print("down")
elif key_code == wx.WXK_LEFT:
print("left")
elif key_code == wx.WXK_RIGHT:
print("right")
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame('appStudy')
frame.Show(True)
return True
if __name__ == "__main__":
app = MyApp()
app.MainLoop()
這個應用有一點點樣子了,通過繼承App和Frame,定義了MyApp和MyFrame,然後就可以在MyFrame裡面加點功能進去了。在這個appStudy應用中,做了下面幾件事情
- 綁定了EVT_PAINT事件和on_paint函式,即app一旦觸發EVT_PAINT事件,就會呼叫on_paint函式。而這個事件在初始化介面的時候是會被呼叫的。所以我們可以把一些繪製圖案的功能放在這個函式裡面。在on_paint函式中
• 設定背景色為淡灰色
• 畫了一個圓角矩形
• 畫了一條線
• 寫了hello world這幾個字 - 綁定了EVT_KEY_DOWN事件和on_key函式,即有按鍵事件發生時,就會呼叫on_key函式。on_key函式中的可以檢測到按的是哪個按鍵
執行這個應用,結果如下
Good,繪製介面和捕捉使用者輸入兩個功能都會了,下面就可以開始開發2048這個小遊戲了。
繪製遊戲背景
第一步,先把遊戲背景給畫出來,這個簡單,就是在on_paint裡面
• 設定個背景色
• 畫一個大矩形
• 大矩形裡面畫16個小矩形
如下圖:
唯一不知道的是矩形顏色怎麼設定,查了一下SetBrush函式可以設定填充色,setPen可以設定邊框。我們不要邊框,直接設定成透明就可以了。Bingo,很容易就畫出來了。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import wx
class MyFrame(wx.Frame):
PANEL_ORIG_POINT = wx.Point(15, 15)
def __init__(self, title):
super(MyFrame, self).__init__(None, title=title, size=(500, 550))
self.Bind(wx.EVT_PAINT, self.on_paint)
self.Centre()
self.SetFocus()
self.Show()
def on_paint(self, e):
self.draw_tiles()
def draw_tiles(self):
dc = wx.ClientDC(self)
dc.SetBackground(wx.Brush("#FAF8EF"))
dc.Clear()
dc.SetBrush(wx.Brush("#C0B0A0"))
dc.SetPen(wx.Pen("", 1, wx.TRANSPARENT))
dc.DrawRoundedRectangle(self.PANEL_ORIG_POINT.x, self.PANEL_ORIG_POINT.y, 450, 450, 5)
for row in range(4):
for column in range(4):
dc.SetBrush(wx.Brush("#CCC0B3"))
dc.DrawRoundedRectangle(self.PANEL_ORIG_POINT.x + 110 * column + 10,
self.PANEL_ORIG_POINT.y + 110 * row + 10, 100, 100, 5)
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame('2048')
frame.Show(True)
return True
if __name__ == "__main__":
app=MyApp()
app.MainLoop()
進一步繪製介面,按鍵事件處理
背景介面畫完之後,就需要考慮實現這幾個功能:
- 處理按鍵事件
- 在小方塊上寫數字,以及根據方塊的不同數字重新整理不同的顏色。
功能1簡單,功能2就寫個測試函式,在按下空格鍵的時候觸發吧。
顏色嘛,就寫個VALUE_COLOR_DEF的map好了,key就是2、4、8……這些數字,value就是顏色。
看起來也不難,得加一個全域性性質的類變數tile_values,用二維陣列的方法來記錄每個小方塊當前的值;然後在空格鍵的時候呼叫test_update_tiles函式給每個小方塊設定個不同的值,然後重新整理一下介面,這一步很容易就寫好了。
實現的過程中遇到個小問題,發現EVT_PAINT經常會觸發,簡單,寫個變數 is_inited來控制讓背景只畫1次。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import wx
class MyFrame(wx.Frame):
PANEL_ORIG_POINT = wx.Point(15, 15)
VALUE_COLOR_DEF = {
0: "#CCC0B3",
2: "#EEE4DA",
4: "#EDE0C8",
8: "#F2B179",
16: "#F59563",
32: "#F67C5F",
64: "#F65E3B",
128: "#EDCF72",
256: "#EDCF72",
512: "#EDCF72",
1024: "#EDCF72",
2048: "#EDCF72",
4096: "#EDCF72",
8192: "#EDCF72",
16384: "#EDCF72",
32768: "#EDCF72"
}
tile_values = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
is_inited = False
def __init__(self, title):
super(MyFrame, self).__init__(None, title=title, size=(500, 550))
self.Bind(wx.EVT_PAINT, self.on_paint)
self.Bind(wx.EVT_KEY_DOWN, self.on_key)
self.Centre()
self.SetFocus()
self.Show()
def on_paint(self, e):
if not self.is_inited:
self.draw_tiles()
self.is_inited = True
def draw_tiles(self):
dc = wx.ClientDC(self)
dc.SetBackground(wx.Brush("#FAF8EF"))
dc.Clear()
dc.SetBrush(wx.Brush("#C0B0A0"))
dc.SetPen(wx.Pen("", 1, wx.TRANSPARENT))
dc.DrawRoundedRectangle(self.PANEL_ORIG_POINT.x, self.PANEL_ORIG_POINT.y, 450, 450, 5)
for row in range(4):
for column in range(4):
tile_value = self.tile_values[row][column]
tile_color = self.VALUE_COLOR_DEF[tile_value]
dc.SetBrush(wx.Brush(tile_color))
dc.DrawRoundedRectangle(self.PANEL_ORIG_POINT.x + 110 * column + 10,
self.PANEL_ORIG_POINT.y + 110 * row + 10, 100, 100, 5)
dc.SetTextForeground("#707070")
text_font = wx.Font(30, wx.SWISS, wx.NORMAL, wx.BOLD, face=u"Roboto")
dc.SetFont(text_font)
if tile_value != 0:
size = dc.GetTextExtent(str(tile_value))
if size[0] > 100:
text_font = wx.Font(24, wx.SWISS, wx.NORMAL, wx.BOLD, face=u"Roboto")
dc.SetFont(text_font)
size = dc.GetTextExtent(str(tile_value))
dc.DrawText(str(tile_value), self.PANEL_ORIG_POINT.x + 110 * column + 10 + (100 - size[0]) / 2,
self.PANEL_ORIG_POINT.y + 110 * row + 10 + (100 - size[1]) / 2)
def on_key(self, event):
key_code = event.GetKeyCode()
if key_code == wx.WXK_UP:
print("UP")
elif key_code == wx.WXK_DOWN:
print("down")
elif key_code == wx.WXK_LEFT:
print("left")
elif key_code == wx.WXK_RIGHT:
print("right")
elif key_code == wx.WXK_SPACE:
self.test_update_tiles()
def test_update_tiles(self):
self.tile_values = [[0, 2, 4, 8], [16, 32, 64, 128], [256, 512, 1024, 2048], [4096, 8192, 16384, 32768]]
self.draw_tiles()
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame('2048')
frame.Show(True)
return True
if __name__ == "__main__":
app = MyApp()
app.MainLoop()
執行一下,按一下空格鍵,不錯,就是我想要的。不好意思的是,這裡的顏色是下了個2048的小遊戲,玩出了所有顏色之後截圖,然後用畫板的拾色器取出來的顏色。
遊戲演算法的實現
到了這裡,最重要的來了,需要實現按鍵之後的演算法了。需要做這幾件事情:
- 使用者按鍵之後,要計算出每個小方塊中新的數字
- 在初始化遊戲時或者按鍵處理之後,需要隨機生成2或者4的數字,填充到空的方塊中。
對於演算法1,以按左鍵為例,我發現每一行都各自獨立的,所以針對每一行進行計算就行了。所以我設計出來的演算法分兩步走:
- 把相鄰需要合併的數字合併
- 把所有非0的格子向左移動,最後的格子填充0
舉例如下:
[2, 2, 4, 4 ] -> [4, 0, 8, 0] -> [4, 8, 0, 0]
如果是按右鍵,就把這一行逆序之後,再按照向左移動處理,處理完之後再逆序一下就行了,如下:
[2, 2, 4, 4 ] -> [4, 4, 2, 2] -> [8, 0, 4, 0] -> [8, 4, 0, 0] -> [0, 0, 4, 8 ]
如果是按上下鍵,就把整個tile_values矩陣行列轉換一下,然後就可以按照左右按鍵處理了,處理完之後再行列轉換回來
對於功能2的實現,新增一個add_random_tile的函式,在所有空(值為0)的小方塊中隨機挑一個,然後再隨機生成2或者4填充進去就可以了。
功能更復雜了,順便調整了一下程式碼結構,於是現在的程式碼變成了下面這個樣子:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import wx
import copy
import random
class MyFrame(wx.Frame):
PANEL_ORIG_POINT = wx.Point(15, 15)
VALUE_COLOR_DEF = {
0: "#CCC0B3",
2: "#EEE4DA",
4: "#EDE0C8",
8: "#F2B179",
16: "#F59563",
32: "#F67C5F",
64: "#F65E3B",
128: "#EDCF72",
256: "#EDCF72",
512: "#EDCF72",
1024: "#EDCF72",
2048: "#EDCF72",
4096: "#EDCF72",
8192: "#EDCF72",
16384: "#EDCF72",
32768: "#EDCF72"
}
tile_values = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
is_inited = False
def __init__(self, title):
super(MyFrame,self).__init__(None, title=title, size=(500, 550))
self.Bind(wx.EVT_PAINT, self.on_paint)
self.Bind(wx.EVT_KEY_DOWN, self.on_key)
self.Centre()
self.SetFocus()
self.Show()
def on_paint(self, e):
if not self.is_inited:
self.start_game()
self.is_inited = True
def add_random_tile(self):
empty_tiles = [(row, col) for row in range(len(self.tile_values)) for col in range(len(self.tile_values[0]))
if self.tile_values[row][col] == 0]
if len(empty_tiles) != 0:
row, col = empty_tiles[random.randint(0, len(empty_tiles) - 1)]
# value should be 2 or 4
self.tile_values[row][col] = 2 ** random.randint(1, 2)
return True
else:
return False
def on_key(self, event):
key_code = event.GetKeyCode()
temp_tile_values = copy.deepcopy(self.tile_values)
if key_code == wx.WXK_UP:
self.on_key_up()
elif key_code == wx.WXK_DOWN:
self.on_key_down()
elif key_code == wx.WXK_LEFT:
self.on_key_left()
elif key_code == wx.WXK_RIGHT:
self.on_key_right()
elif key_code == wx.WXK_SPACE:
self.test_update_tiles()
return
self.add_random_tile()
self.draw_tiles()
def update_single_row_value(self, row_value, positive):
num_cols = len(row_value)
if not positive:
temp_data = copy.deepcopy(row_value)
row_value = [temp_data[num_cols - 1 - i] for i in range(num_cols)]
for i in range(num_cols):
if row_value[i] == 0:
continue
for j in range(i + 1, num_cols):
if row_value[j] == row_value[i]:
row_value[i] *= 2
row_value[j] = 0
break
elif row_value[j] > row_value[i]:
break
for i in range(num_cols):
if row_value[i] != 0:
continue
for j in range(i + 1, num_cols):
if row_value[j] != 0:
row_value[i] = row_value[j]
row_value[j] = 0
break
if not positive:
temp_data = copy.deepcopy(row_value)
row_value = [temp_data[num_cols - 1 - i] for i in range(num_cols)]
return row_value
def on_key_up(self):
temp_tile_values = [[row[i] for row in self.tile_values] for i in range(len(self.tile_values[0]))]
for row in range(len(self.tile_values)):
temp_tile_values[row] = self.update_single_row_value(temp_tile_values[row], True)
self.tile_values = [[row[i] for row in temp_tile_values] for i in range(len(temp_tile_values[0]))]
def on_key_down(self):
temp_tile_values = [[row[i] for row in self.tile_values] for i in range(len(self.tile_values[0]))]
for row in range(len(self.tile_values)):
temp_tile_values[row] = self.update_single_row_value(temp_tile_values[row], False)
self.tile_values = [[row[i] for row in temp_tile_values] for i in range(len(temp_tile_values[0]))]
def on_key_left(self):
for row in range(len(self.tile_values)):
self.tile_values[row] = self.update_single_row_value(self.tile_values[row], True)
def on_key_right(self):
for row in range(len(self.tile_values)):
self.tile_values[row] = self.update_single_row_value(self.tile_values[row], False)
def init_screen(self):
dc = wx.ClientDC(self)
dc.SetBackground(wx.Brush("#FAF8EF"))
dc.Clear()
dc.SetBrush(wx.Brush("#C0B0A0"))
dc.SetPen(wx.Pen("", 1, wx.TRANSPARENT))
dc.DrawRoundedRectangle(self.PANEL_ORIG_POINT.x, self.PANEL_ORIG_POINT.y, 450, 450, 5)
def draw_tiles(self):
dc = wx.ClientDC(self)
dc.SetBackground(wx.Brush("#F0F0E0"))
dc.SetBrush(wx.Brush("#C0B0A0"))
dc.SetPen(wx.Pen("", 1, wx.TRANSPARENT))
for row in range(4):
for column in range(4):
tile_value = self.tile_values[row][column]
tile_color = self.VALUE_COLOR_DEF[tile_value]
dc.SetBrush(wx.Brush(tile_color))
dc.DrawRoundedRectangle(self.PANEL_ORIG_POINT.x + 110 * column + 10,
self.PANEL_ORIG_POINT.y + 110 * row + 10, 100, 100, 5)
dc.SetTextForeground("#707070")
text_font = wx.Font(30, wx.SWISS, wx.NORMAL, wx.BOLD, face=u"Roboto")
dc.SetFont(text_font)
if tile_value != 0:
size = dc.GetTextExtent(str(tile_value))
if size[0] > 100:
text_font = wx.Font(24, wx.SWISS, wx.NORMAL, wx.BOLD, face=u"Roboto")
dc.SetFont(text_font)
size = dc.GetTextExtent(str(tile_value))
dc.DrawText(str(tile_value), self.PANEL_ORIG_POINT.x + 110 * column + 10 + (100 - size[0]) / 2,
self.PANEL_ORIG_POINT.y + 110 * row + 10 + (100 - size[1]) / 2)
def start_game(self):
self.tile_values = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
self.init_screen()
self.add_random_tile()
self.add_random_tile()
self.draw_tiles()
def test_update_tiles(self):
self.tile_values = [[0, 2, 4, 8], [16, 32, 64, 128], [256, 512, 1024, 2048], [4096, 8192, 16384, 32768]]
self.draw_tiles()
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame('2048')
frame.Show(True)
return True
if __name__ == "__main__":
app = MyApp()
app.MainLoop()
做到這裡,遊戲已經可以玩了,好開心^_^。可是遊戲還不會判斷是否結束。
結束判斷
同時滿足下面2個條件,就可以判定遊戲結束了:
- 所有格子的值全都是非0
- 沒有任意兩個相鄰的格子值相同,相鄰包括上下左右
於是加一個函式is_game_over來實現這個功能,如果確定已經結束了,那就彈出個提示框來讓使用者重新開始。兩個函式設計如下:
def is_game_over(self):
# exist 0 or there is a neighbour with the same value
print ("is_game_over")
num_rows = len(self.tile_values)
num_cols = len(self.tile_values[0])
for i in range(num_rows):
for j in range(num_cols):
if self.tile_values[i][j] == 0 or \
(j < num_cols-1 and self.tile_values[i][j] == self.tile_values[i][j + 1]) or \
(i < num_rows-1 and self.tile_values[i][j] == self.tile_values[i + 1][j]):
return False
return True
def game_over(self):
if wx.MessageBox(u"遊戲結束,是否再來一局?", u"Game Over", wx.YES_NO) == wx.YES:
self.start_game()
增加計分和最高記錄功能
沒有分數這遊戲可不好玩,得把這個加上。分數的計算,就每次把被合併了的數字加到總分上去好了。介面設計成如下:
直接畫文字也可以,不過我發現個更好的工具:StaticText控制元件。用這個控制元件之後,更新分數就只要修改控制元件內容就可以。這一步要做下面幾件事:
- 初始化介面的時候繪製4個StaticText控制元件
- 用一個檔案來加下“記錄”的分數,開始遊戲的時候要讀出分數,結束遊戲的時候,如果破紀錄了要更新檔案
- 在每次按鍵的時候,要計算當前分數。如果分數有變化,需要更新介面上的“積分”
因為在最上方增加了4個控制元件,所以整個遊戲介面也往下移
增加重新開始功能
對比我手機上下載的2048的遊戲,還缺少一個重新開始按鈕,那就把這個功能加上吧,於是介面就變成了下面這個樣子。
按鈕使用button控制元件實現。最終程式碼變成了:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import wx
import copy
import random
'''
1. 繪製空介面,即學習程式語言的基本功能
2. 繪製4×4的方塊
3. 捕捉上下左右按鍵,測試是否可以更新方塊顏色和數字
4. 初始化介面(random 兩個位置,再random兩個數字2或者4放入位置),在按鍵事件中增加演算法,更新對應方塊的數字和顏色
5. 增加結束條件
6. 增加計分和最高記錄功能
7、增加重新開始按鈕
'''
class MyFrame(wx.Frame):
PANEL_ORIG_POINT = wx.Point(15, 100)
VALUE_COLOR_DEF = {
0: "#CCC0B3",
2: "#EEE4DA",
4: "#EDE0C8",
8: "#F2B179",
16: "#F59563",
32: "#F67C5F",
64: "#F65E3B",
128: "#EDCF72",
256: "#EDCF72",
512: "#EDCF72",
1024: "#EDCF72",
2048: "#EDCF72",
4096: "#EDCF72",
8192: "#EDCF72",
16384: "#EDCF72",
32768: "#EDCF72"
}
tile_values = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
is_inited = False
score = 0
record = 0
def __init__(self, title):
super(MyFrame, self).__init__(None, title=title, size=(500, 600))
self.init_widgets()
self.Bind(wx.EVT_PAINT, self.on_paint)
self.Bind(wx.EVT_KEY_DOWN, self.on_key)
self.Centre()
self.SetFocus()
self.Show()
def on_paint(self, e):
if not self.is_inited:
self.start_game()
self.is_inited = True
def add_random_tile(self):
empty_tiles = [(row, col) for row in range(len(self.tile_values)) for col in range(len(self.tile_values[0]))
if self.tile_values[row][col] == 0]
if len(empty_tiles) != 0:
row, col = empty_tiles[random.randint(0, len(empty_tiles) - 1)]
# value should be 2 or 4
self.tile_values[row][col] = 2 ** random.randint(1, 2)
return True
else:
return False
def on_key(self, event):
key_code = event.GetKeyCode()
temp_tile_values = copy.deepcopy(self.tile_values)
if key_code == wx.WXK_UP:
self.on_key_up()
elif key_code == wx.WXK_DOWN:
self.on_key_down()
elif key_code == wx.WXK_LEFT:
self.on_key_left()
elif key_code == wx.WXK_RIGHT:
self.on_key_right()
elif key_code == wx.WXK_SPACE:
self.test_update_tiles()
return
if temp_tile_values == self.tile_values:
if self.is_game_over():
self.game_over()
else:
self.draw_score()
self.add_random_tile()
self.draw_tiles()
def update_single_row_value(self, row_value, positive):
num_cols = len(row_value)
if not positive:
temp_data = copy.deepcopy(row_value)
row_value = [temp_data[num_cols - 1 - i] for i in range(num_cols)]
for i in range(num_cols):
if row_value[i] == 0:
continue
for j in range(i + 1, num_cols):
if row_value[j] == row_value[i]:
self.score += row_value[j]
row_value[i] *= 2
row_value[j] = 0
break
elif row_value[j] > row_value[i]:
break
for i in range(num_cols):
if row_value[i] != 0:
continue
for j in range(i + 1, num_cols):
if row_value[j] != 0:
row_value[i] = row_value[j]
row_value[j] = 0
break
if not positive:
temp_data = copy.deepcopy(row_value)
row_value = [temp_data[num_cols - 1 - i] for i in range(num_cols)]
return row_value
def on_key_up(self):
temp_tile_values = [[row[i] for row in self.tile_values] for i in range(len(self.tile_values[0]))]
for row in range(len(self.tile_values)):
temp_tile_values[row] = self.update_single_row_value(temp_tile_values[row], True)
self.tile_values = [[row[i] for row in temp_tile_values] for i in range(len(temp_tile_values[0]))]
def on_key_down(self):
temp_tile_values = [[row[i] for row in self.tile_values] for i in range(len(self.tile_values[0]))]
for row in range(len(self.tile_values)):
temp_tile_values[row] = self.update_single_row_value(temp_tile_values[row], False)
self.tile_values = [[row[i] for row in temp_tile_values] for i in range(len(temp_tile_values[0]))]
def on_key_left(self):
for row in range(len(self.tile_values)):
self.tile_values[row] = self.update_single_row_value(self.tile_values[row], True)
def on_key_right(self):
for row in range(len(self.tile_values)):
self.tile_values[row] = self.update_single_row_value(self.tile_values[row], False)
def on_btn_restart(self, e):
self.game_over()
def init_widgets(self):
self.label_score_text = wx.StaticText(self, -1, u"積分", (50, 15), (100, 30), wx.ALIGN_CENTER)
self.label_score_text.Font = wx.Font(24, wx.SWISS, wx.NORMAL, wx.BOLD, face=u"Roboto")
self.label_score_text.SetForegroundColour("#0000A0")
self.label_score_text.SetBackgroundColour("#FAF8EF")
self.label_record_text = wx.StaticText(self, -1, u"記錄", (200, 15), (100, 30), wx.ALIGN_CENTER)
self.label_record_text.Font = wx.Font(24, wx.SWISS, wx.NORMAL, wx.BOLD, face=u"Roboto")
self.label_record_text.SetForegroundColour("#0000A0")
self.label_record_text.SetBackgroundColour("#FAF8EF")
self.score_text = wx.StaticText(self, -1, "0", (50, 50), (100, 30), wx.ALIGN_CENTER)
self.score_text.Font = wx.Font(24, wx.SWISS, wx.NORMAL, wx.BOLD, face=u"Roboto")
self.score_text.SetForegroundColour("#0000A0")
self.record_text = wx.StaticText(self, -1, str(self.record), (200, 50), (100, 30), wx.ALIGN_CENTER)
self.record_text.Font = wx.Font(24, wx.SWISS, wx.NORMAL, wx.BOLD, face=u"Roboto")
self.record_text.SetForegroundColour("#0000A0")
self.restart_btn = wx.Button(self, -1, u"重新\n開始", (350, 10), (80, 80), wx.ALIGN_CENTER)
self.restart_btn.Font = wx.Font(24, wx.SWISS, wx.NORMAL, wx.BOLD, face=u"Roboto")
self.restart_btn.SetForegroundColour("#0000A0")
self.restart_btn.Bind(wx.EVT_BUTTON, self.on_btn_restart)
def init_screen(self):
dc = wx.ClientDC(self)
dc.SetBackground(wx.Brush("#FAF8EF"))
dc.Clear()
dc.SetBrush(wx.Brush("#C0B0A0"))
dc.SetPen(wx.Pen("", 1, wx.TRANSPARENT))
dc.DrawRoundedRectangle(self.PANEL_ORIG_POINT.x, self.PANEL_ORIG_POINT.y, 450, 450, 5)
self.score_text.SetLabel("0")
self.record_text.SetLabel(str(self.record))
def draw_score(self):
self.score_text.SetLabel(str(self.score))
def draw_tiles(self):
dc = wx.ClientDC(self)
dc.SetBackground(wx.Brush("#F0F0E0"))
dc.SetBrush(wx.Brush("#C0B0A0"))
dc.SetPen(wx.Pen("", 1, wx.TRANSPARENT))
for row in range(4):
for column in range(4):
tile_value = self.tile_values[row][column]
tile_color = self.VALUE_COLOR_DEF[tile_value]
dc.SetBrush(wx.Brush(tile_color))
dc.DrawRoundedRectangle(self.PANEL_ORIG_POINT.x + 110 * column + 10,
self.PANEL_ORIG_POINT.y + 110 * row + 10, 100, 100, 5)
dc.SetTextForeground("#707070")
text_font = wx.Font(30, wx.SWISS, wx.NORMAL, wx.BOLD, face=u"Roboto")
dc.SetFont(text_font)
if tile_value != 0:
size = dc.GetTextExtent(str(tile_value))
if size[0] > 100:
text_font = wx.Font(24, wx.SWISS, wx.NORMAL, wx.BOLD, face=u"Roboto")
dc.SetFont(text_font)
size = dc.GetTextExtent(str(tile_value))
dc.DrawText(str(tile_value), self.PANEL_ORIG_POINT.x + 110 * column + 10 + (100 - size[0]) / 2,
self.PANEL_ORIG_POINT.y + 110 * row + 10 + (100 - size[1]) / 2)
def start_game(self):
self.tile_values = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
self.score = 0
try:
with open("record.txt") as fp:
self.record = int(fp.read())
# print("read record: %d" % self.record)
except (IOError, ValueError), err:
print("read record error: %s" % err)
self.record = 0
self.init_screen()
self.add_random_tile()
self.add_random_tile()
self.draw_tiles()
def is_game_over(self):
# exist 0 or there is a neighbour with the same value
# print ("is_game_over")
num_rows = len(self.tile_values)
num_cols = len(self.tile_values[0])
for i in range(num_rows):
for j in range(num_cols):
if self.tile_values[i][j] == 0 or \
(j < num_cols - 1 and self.tile_values[i][j] == self.tile_values[i][j + 1]) or \
(i < num_rows - 1 and self.tile_values[i][j] == self.tile_values[i + 1][j]):
return False
return True
def game_over(self):
if self.score > self.record:
self.record = self.score
try:
with open("record.txt", "w") as fp:
fp.write(str(self.score))
except IOError as err:
print err
if wx.MessageBox(u"遊戲結束,是否再來一局?", u"Game Over", wx.YES_NO) == wx.YES:
self.start_game()
def test_update_tiles(self):