Chương trình được thực hiện trên Simulator của Xcode 5.1.1. Chạy trên máy MACBOOK 2012 sử dụng chip Core i7 2.6GHz thu được kết quả sau:
Rằng buộc đầu vào:
- Với mỗi bước sinh ngẫu nhiên các số 2 hoặc 4 với tỉ lệ, số 2: chiếm 90% số 4 chiếm 10%
Kết quả thu đƣợc:
Hình 3.7: Màn hình hiển thị kết quả trong quá trình chạy thử nghiệm
Độ sâu Mục tiêu Số lần
chạy Tổng thời gian trung bình Thời gian chiến Tỉ lệ thắng
2 2048 50 93.5’ 1.876’ 67%
4096 40 3h43’ 5.575’ 11%
3 2048 30 2h41’ 5.3’ 98%
4096 10 1h37’ 9.7’ 70%
Thời gian trong bảng trên bao gồm cả thời gian cho quá trình đồ hoạ của trò chơi, các hiệu ứng vẽ và di chuyển các trạng thái trong trò chơi từ trạng thái
ban đầu đến trạng thái kết thúc (dành chiến thắng hoặc thất bại). Chính vì thế đó không chỉ là thời gian tính toán của giải thuật Minimax alpha-beta mà còn gồm cả thời gian của việc đồ hoạ cho trò chơi.
3.2.5 Quan sát quá trình chơi tự động và một số kinh nghiệm thu đƣợc
Trong quá trình máy tự động chơi, ta quan sát được các quyết định di chuyển tối ưu ở độ sâu định trước. Trong những bước tính toán tối ưu đó các trạng thái của trò chơi 2048 thường biến đổi theo một số quy luật và có những điểm chung nhất định. Từ đó ta có thể đúc kết lại một số kinh nghiệm cho người chơi để khả năng dành chiến thắng của người chơi là cao nhất. Dưới đây là một số kinh nghiệm quan sát được trong quá trình cho máy chơi tự động.
1. Di chuyển theo 3 hƣớng định sẵn.
Hình 3.8(a): Màn hình hiển thị một trạng thái kết quả của việc chọn cách di chuyển và xây dựng nền tảng các con số ở hàng dưới cùng
Người chơi sẽ cố gắng di chuyển bảng số theo ba hướng nhất đi ̣nh ví dụ: trái, phải và xuống. Nếu theo ba hướng này, người chơi sẽ có thể xây dựng được một nền tảng khá tốt ở phía dưới và sau đó ngư ời chơi chỉ sẽ mất một chút thời gian với mô ̣t vài chuyển đô ̣ng đôi cùng mô ̣t chuỗi dây chuyền kết nối sẽ giúp những con số gô ̣p la ̣i với nhau thì mô ̣t số có giá tri ̣ lớn . Hãy ưu tiên di chuyển theo ba
hướng định trước và lặp lại nó như một chiến thuật để liên kết các con số giống nhau la ̣i với nhau.
Khi đã định rõ chiến lược chơi cho việc di chuyển theo ba hướng nhất định thì phải hạn chế tối đa việc di chuyển theo hướng thứ tư. Di chuyển theo hướng thứ tư có thể sẽ làm mất lợi thế và rời rạc hoá những con số đang có giá trị cao mà người chơi đã mất rất nhiều công để tạo ra và những con số ấy đang ở những vị trí thuận lợi có khả năng hợp nhất với nhau.
2. Cố gắng cân bằng giá trị của các con số trong bảng
Hình 3.8(b): Màn hình hiển thị một trạng thái kết quả của việc chọn cách di chuyển để cố gắng cân bằng các con số
Đây là mô ̣t chiến lược tốt khi ngư ời chơi cân bằng được sự tăng trưởng của các con số, khi trong ván chơi không có sự chênh lê ̣ch quá nhiều giữa các con số lớn nhất và nhỏ nhất . Hãy cố gắng để nhận được 2 con số trả về cao nhất ta ̣i mo ̣i thời điểm, theo sau đó là mô ̣t số lượng con số nhỏ hơn xuất hiê ̣n theo că ̣p sẵn sàng kết hợp với nhau trong mo ̣i thời điểm. Nếu người chơi có thể duy trì được tính cân bằng trong trò chơi, người chơi sẽ có rất nhiều cơ hội để dành chiến thắng. Rõ ràng việc cân bằng giá trị các con số là nền tảng giúp việc hợp nhất
được dễ dàng. Nếu trong khi chơi, người chơi không tạo được sự cân bằng mà để giả trị các con số khác biệt và rời rạc thì khả năng thua cuộc là rất lớn
3. Giảm số lần di chuyển không có hợp nhất
Hình 3.8(c): Màn hình hiển thị một trạng thái kết quả của việc chọn cách chơi giảm số lần di chuyển trống
Mô ̣t bước di chuyển trống ở đây có thể hiểu là bước di chuyển mà không gô ̣p được bất kỳ con số nào . Người chơi cần cố gắng giảm bớt những di chuyển như thế này vì mỗi lần ngư ời chơi di chuyển sẽ phát sinh mô ̣t con số ở nhữn g vi ̣ trí ngẫu nhiên. Khi người chơi không thể gô ̣p được những con số thì đừng nên làm nó phát sinh thêm mới quá nhiều . Trong quá trình di chuyển không có hợp nhất thì điểm số cũng sẽ không tang, chính vì vậy thuật toán chạy tự động cũng luôn tự đúng và loại bỏ cách di chuyển này.
Hình 3.8(d): Màn hình hiển thị một trạng thái kết quả của việc chọn cách chơi xây dựng những nền tảng nhỏ
Người chơi sẽ nhận được một số lượng các con số phát sinh khá nhiều sau mỗi lần di chuyển trong mô ̣t thời gian dài , vì thế hãy ưu tiên kết hợp các con số này lại với nhau và cân bằng chúng ở mức ban đầu . Sẽ rất dễ dàng cho những bước tiếp theo nếu ta xây dựng cách chơi để hợp nhất được những nền tảng nhỏ.
5. Di chuyển toàn bộ bảng số
Hình 3.8(e): Màn hình minh hoạ cho chiến lược di chuyển toàn bộ bảng số Khi người chơi di chuyển ở bất cứ hướng nào, các con số mới cũng sẽ được phát sinh. Chúng không hoàn toàn là những con số giống nhau. Nhưng ý tưởng ở đây là hãy thử và kết hợp nhiều hình vuông nhất có thể trong mỗi bước di chuyển và
cố gắng tối đa hóa không gian trống khi có thể . Khi có nhiều khoảng trống , người chơi sẽ có nhiều hướng lựa chọn hơn để di chuyển.
KẾT LUẬN
Với mục tiêu đề ra của luận văn là tìm hiểu và nghiên cứu về thuật toán tìm kiếm đối kháng Minimax, các cải tiến của nó và ứng dụng trong trò chơi 2048, các kết luận chính đã đạt được của luận văn có thể tóm tắt như sau:
Đã tìm hiểu được tổng quan về lý thuyết trò chơi và phương pháp tìm kiếm Minimax alpha-beta trong các trò chơi đối kháng.
Đưa ra mô hình toán học áp dụng vào trò chơi 2048.
Xây dựng lại trò chơi 2048 trên nền tảng iOS và quan trọng hơn là áp dụng lý thuyết đã tìm hiểu về thuật toán Minimax alpha-beta để xây dựng chức năng cho máy tự động chơi.
Xây dựng chức năng chỉ dẫn người chơi cho trò chơi 2048. Chức năng này bản chất cũng là việc để máy chạy thuật toán Minimax alpha-beta với trạng thái đầu vào là trạng thái của bảng hiện tại mà người chơi đang cần máy chỉ nước đi.
TÀI LIỆU THAM KHẢO
Tiếng Việt
1. Nguyễn Thị Lệ (2009) Giải thuật tìm kiếm Minimax và ứng dụng trong các trò chơi có tổng bằng không, ĐHKHTN-ĐHQGHN
2. Phạm Thị Anh Lê, Phạm Thọ Hoàn (2011) Trí tuệ nhân tạo Artificial intelligence, Khoa CNTT trường ĐHSP Hà nội
3. Đỗ Xuân Lôi (1998), Cấu trúc dữ liệu và giải thuật, NXB Khoa học kỹ thuật, Hà Nội.
4. Nguyễn Đức Nghĩa - Nguyễn Tô Thành (1997), Toán rời rạc, NXB Giáo dục.
5. Đinh Mạnh Tường (2001), Cấu trúc dữ liệu & Thuật toán, NXB Khoa học kĩ thuật, Hà nội.
6. Đinh Mạnh Tường (2002), Trí tuệ nhân tạo, NXB Khoa học kỹ thuật, Hà nội.
Tiếng Anh
7. Fudenberg, Drew and Jean Tirole: Game Theory, MIT Press, 1991
8. Poundstone, William Prisoner's Dilemma: John von Neumann, Game Theory and the Puzzle of the Bomb.
9. Nash, John (1950) "Equilibrium points in n-person games" Proceedings of the National Academy of Sciences
10. Jessica Billings (2008), The Minimax Algorithm, CS 330. 11. Michael A. Goodrich (2007), Proof of the Minimax Theorem. 12. Heylighen (1993), Zero sum games – Principia Cybernetica Web.
PHỤ LỤC
Chi tiết cài đặt một số lớp quan trọng trong chương trình
Lớp M2GlobalState:
Một số thuộc tính đáng chú ý:
@property (nonatomic, readonly) NSInteger dimension; @property (nonatomic, readonly) NSInteger winningLevel; @property (nonatomic, readonly) NSInteger tileSize; @property (nonatomic, readonly) NSInteger borderWidth; @property (nonatomic, readonly) NSInteger cornerRadius; @property (nonatomic, readonly) NSInteger horizontalOffset; @property (nonatomic, readonly) NSInteger verticalOffset;
@property (nonatomic, readonly) NSTimeInterval animationDuration; @property (nonatomic, readonly) NSTimeInterval searchTimeOut; @property (nonatomic, readonly) NSTimeInterval maxSearchDepth; Ý nghĩa :
@property (nonatomic, readonly) NSInteger dimension: Chương trình xây dựng trò chơi 2048 hỗ trợ các kích thước bảng chơi 3x3 4x4 5x5. Thuộc tính này lưu trữ kích thước của bảng chơi
@property (nonatomic, readonly) NSInteger winningLevel: Lưu trữ mốc dành chiến thẳng của trò chơi, mặc định là 2048 tuy nhiên giá trị này có thể tuỳ biến theo người dùng. @property (nonatomic, readonly) NSInteger tileSize: Kích thước của mỗi ô hiển thị trên màn hình iPhone
@property (nonatomic, readonly) NSTimeInterval animationDuration: Thuộc tính liên quan đến quá trình hiển thị UI cho người chơi khi người chơi thực hiện một nước đi thì có hiệu ứng di chuyển các title.
@property (nonatomic, readonly) NSTimeInterval searchTimeOut: Thuộc tính rất quan trọng lien quan đến thuật toán Minimax alpha-beta cài đặt cho máy tự chơi. Thuộc tính này giới hạn thời gian tìm kiếm tối đa cho một nước đi.
@property (nonatomic, readonly) NSTimeInterval maxSearchDepth: Thuộc tính này lưu trữ độ sâu của giải thuật Minimax alpha-beta mà người dùng muốn tìm kiếm. Độ sâu này được thay đổi trong màn hình cài đặt của trò chơi.
Lớp M2AI :
- (instancetype)initWithGrid:(M2Grid *)grid; - (M2Vector *)bestMove;
-(id)initWithMove:(M2Vector*)bestMove score:(double)score positions:(NSInteger)positions cutoffs:(NSInteger)cutoffs;
- (M2AIResult *)searchWithPlayerTurn:(BOOL)playerTurn depth:(NSInteger)depth
alpha:(double)alpha beta:(double)beta positions:(NSInteger)positions cutoffs:(NSInteger)cutoffs;
- (BOOL)isWinningBoard; - (BOOL)isGameOver;
- (M2Grid *)gridAfterMoveInDirection:(M2Vector *)direction;
Ý nghĩa của các phƣơng thức trên:
- (instancetype)initWithGrid:(M2Grid *)grid: Khởi tạo đầu vào cho lớp AI là một trạng thái của trò chơi 2048.
- (M2Vector *)bestMove: Phương thức này cho trả về kết quả nước đi tốt nhất cho trạng thái đầu vào ở phương thức bên trên.
- (id)initWithMove:(M2Vector *)bestMove score:(double)score
positions:(NSInteger)positions cutoffs:(NSInteger)cutoffs: Khởi tạo đầu vào cho lớp AI để tính toán nước đi tốt nhất cho một trạng thái mà khi đó đã biết giá trị tốt nhất trạng thái đó có thể đạt được cho đến thời điểm xét (score) vị trí của tốt nhất của nó
(positions) và nhánh được cắt bỏ (cutoffs)
- (M2AIResult *)searchWithPlayerTurn:(BOOL)playerTurn depth:(NSInteger)depth
alpha:(double)alpha beta:(double)beta positions:(NSInteger)positions cutoffs:(NSInteger)cutoffs: Hàm chính của thuật toán Minimax alpha-beta:
o playerTurn: Thể hiện người chơi Min hay Max o depth: Độ sâu đang xét trong quá trình tìm kiếm
o alpha: hệ số alpha trong thuật toán Minimax alpha-beta o beta: Hệ số beta trong thuật toán Minimax alpha-beta
o cutoffs: Đánh dấu nhánh được cắt bỏ trong quá trình tìm kiếm
- (BOOL)isWinningBoard: Kiếm tra trạng thái hiện tại xem đã là trạng thái chiến thắng hay chưa
hiện tại.
- (M2Grid *)gridAfterMoveInDirection:(M2Vector *)direction: Trả về trạng thái mới của trò chơi 2048 nếu di chuyển trạng thái hiện tại bằng phép di chuyển direction.
Class M2GameManager:
Các phƣơng thức đáng chú ý:
- (void)startNewSessionWithScene:(M2Scene *)scene; - (void)moveToDirection:(M2Direction)direction; - (void)showHint;
- (void)autoRun;
Ý nghĩa:
- (void)startNewSessionWithScene:(M2Scene *)scene: Tạo mới trò chơi.
- (void)moveToDirection:(M2Direction)direction: Hiển thị trạng thái thay đổi bằng cách di chuyển người dùng lựa chọn.
- (void)showHint: Gọi đến AI tính toán theo Minimax alpha-beta để đưa ra nước đi tối ưu và hiển thị chỉ dẫn người chơi sẽ đi theo cách tối ưu máy tìm được.
- (void)autoRun: Hàm cho máy tự động chơi trò chơi 2048.
Chi tiết cài đặt một số hàm quan trọng trong chƣơng trình
- (M2Vector *)bestMove {
// Khai báo kết quả tối ưu tìm được
M2AIResult *newBest;
// Khởi tạo thời gian bắt đầu tìm kiếm
_startTime = [NSDate date]; @autoreleasepool {
M2AIResult *result; // Tính toán từ độ sâu 0
for (NSInteger depth = 0; depth <= GSTATE.maxSearchDepth; depth++) { // Quá thời gian cho phép
if (ABS([_startTimetimeIntervalSinceNow]) > GSTATE.searchTimeOut) break; result = [self searchWithPlayerTurn:YES depth:depth alpha:-1000000beta:1000000 positions:0cutoffs:0];
newBest = result; }
} }
return newBest.move;
Hàm Minimax alpha-beta
- (M2AIResult *)searchWithPlayerTurn:(BOOL)playerTurn depth:(NSInteger)depth
alpha:(double)alpha beta:(double)beta positions:(NSInteger)positions cutoffs:(NSInteger)cutoffs {
if (ABS([_startTimetimeIntervalSinceNow]) > GSTATE.searchTimeOut) return nil; double bestScore;
M2Vector *bestMove; M2AIResult *result; if (playerTurn) {
// Nếu là người chơi MAX
bestScore = alpha;
for (M2Vector *direction in M2Vectors) {
if ([self.gridisMovableInDirection:direction]) {
M2Grid *movedGrid = [self.gridgridAfterMoveInDirection:direction]; positions++;
if ([movedGrid isWinningBoard]) {
return [[M2AIResultalloc] initWithMove:direction score:10000 positions:positions cutoffs:cutoffs]; }
if (depth == 0) {
result = [[M2AIResultalloc] initWithMove:direction
score:movedGrid.heuristicValuepositions:0cutoffs:0]; } else {
M2AI *newAI = [[M2AIalloc] initWithGrid:movedGrid]; newAI.startTime = _startTime;
result = [newAI searchWithPlayerTurn:NO depth:depth - 1alpha:bestScore beta:beta positions:positions cutoffs:cutoffs];
if (result.score >= 9900) result.score--; positions = result.positions;
cutoffs = result.cutoffs; }
if (!result) return nil;
if (result.score > bestScore) { bestScore = result.score; bestMove = direction; }
if (bestScore > beta) { cutoffs++;
return [[M2AIResultalloc] initWithMove:bestMove score:beta positions:positions cutoffs:cutoffs]; }
} } } else {
// Nếu là người chơi MIN
bestScore = beta;
NSArray *availableCells = [self.gridavailableCells];
NSMutableDictionary *score1 = [NSMutableDictionary dictionary]; NSMutableDictionary *score2 = [NSMutableDictionary dictionary]; NSDictionary *scores = @{@1: score1, @2: score2};
for (NSNumber *level in @[@1, @2]) {
for (M2Cell *availableCell in availableCells) {
[self.gridinsertDummyTileAtPosition:availableCell.position
withLevel:level.integerValue];
NSMutableDictionary *subscore = scores[level];
subscore[availableCell] = @(-self.grid.smoothness + self.grid.dimension * self.grid.dimension - (availableCells.count - 1));
[self.gridremoveTileAtPosition:availableCell.position]; }
}
NSMutableArray *candidates = [NSMutableArray array];
double maxScore = MAX([[[scores[@1] allValues] valueForKeyPath:@"@max.self"]
doubleValue], [[[scores[@2] allValues] valueForKeyPath:@"@max.self"] doubleValue]);
for (NSNumber *level in scores) { for (M2Cell *cell in scores[level]) {
double score = [scores[level][cell] doubleValue]; if (ABS(score - maxScore) < 0.00001) {
[candidates addObject:@{@"cell": cell, @"level": level}]; }
} }
for (NSDictionary *candidateInfo in candidates) { M2Grid *newGrid = [self.gridcopy];
M2Position candidatePosition = [(M2Cell *)candidateInfo[@"cell"] position]; [newGrid insertDummyTileAtPosition:candidatePosition
withLevel:[candidateInfo[@"level"] integerValue]]; positions++;
newAI.startTime = _startTime;
result = [newAI searchWithPlayerTurn:YES depth:depth alpha:alpha beta:bestScore positions:positions cutoffs:cutoffs];
positions = result.positions; cutoffs = result.cutoffs;
// Nếu hết thời gian
if (!result) return nil;
if (result.score < bestScore) { bestScore = result.score; }
// Cắt tỉa alpha
if (bestScore < alpha) { cutoffs++;
return [[M2AIResultalloc] initWithMove:nil score:alpha positions:positions cutoffs:cutoffs];
} } }
return [[M2AIResultalloc] initWithMove:bestMove score:bestScore positions:positions
cutoffs:cutoffs]; }