- 由來
最近看到小朋友在玩 OOXX 也就是井字遊戲,很簡單,很耐玩的小小遊戲,就想說來implement一下這個東西。
- 井字遊戲
規則很簡單,九宮格,一人畫一個輪流,三個連成線就贏了!誰先手的話,贏面比較大。
- AI
在AI的書中,OOXX幾乎是標準的教材,在wikipedia上面查詢TicTacToe 也有分析的步驟 http://en.wikipedia.org/wiki/Tic-tac-toe ,所以就加上AI 的功能,這個 AI 很容易就可以走完所有的走法,從中選出最好的,所以 AI 是不敗的,反而人類一不小心就會犯錯!
AI 的方法就是標準的Game tree search 進化成 Minmax ( http://en.wikipedia.org/wiki/Minmax )
,再簡化成 Negamax ( http://en.wikipedia.org/wiki/Negamax ),後面還有 Alpha Beta Pruning 的方式來加快運算的速度,不過因為只有九宮格,很容易就運算完畢,這部分就省下了!實際上implement 時第一步需要記下來走法和分數,用來決定下一步怎麼走,也是就 rootsearch function。
後面有加上random 來選出相同分數的走法,以免AI 都是走同一步,但是因為都是選最高分,所以 AI 還是不敗的,呵呵!
AI 的方法就是標準的Game tree search 進化成 Minmax ( http://en.wikipedia.org/wiki/Minmax )
,再簡化成 Negamax ( http://en.wikipedia.org/wiki/Negamax ),後面還有 Alpha Beta Pruning 的方式來加快運算的速度,不過因為只有九宮格,很容易就運算完畢,這部分就省下了!實際上implement 時第一步需要記下來走法和分數,用來決定下一步怎麼走,也是就 rootsearch function。
後面有加上random 來選出相同分數的走法,以免AI 都是走同一步,但是因為都是選最高分,所以 AI 還是不敗的,呵呵!
package com.robin.tictactoe;
import java.util.ArrayList;
import java.util.Date;
public class AI {
char marker;
char opponentmarker;
long initTime = 0;
long nowTime = 0;
int number = 0;
char type = 'C';
public AI(char marker) {
this.marker = marker;
if(marker == 'X')
opponentmarker = 'O';
else
opponentmarker = 'X';
}
void move(Game game) {
initTime = new Date().getTime();
number = 0;
int move = rootsearch(game, 1);
game.move(marker, move);
}
int get_score(Game game) {
number++;
if(game.is_gameover()) {
if(game.winner==marker)
return 1;
else if(game.winner==opponentmarker)
return -1;
}
return 0;
}
int rootsearch(Game game, int color) {
int bestmove = -999;
int bestscore = -999;
// get the possible move
ArrayList moves = game.get_move();
ArrayList candidate = new ArrayList();
for(int i=0; i bestscore) {
bestscore = score;
bestmove = moves.get(i);
candidate.clear();
candidate.add(moves.get(i));
} else if(score == bestscore) {
candidate.add(moves.get(i));
}
}
bestmove = candidate.get((int)Math.floor(Math.random()*candidate.size()));
return bestmove;
}
int negamax(Game game, int color) {
int bestscore = -999;
// get the score
if(game.is_gameover()) {
bestscore = color * get_score(game);
} else {
//get the possible moves
ArrayList moves = game.get_move();
for(int i=0; i bestscore) {
bestscore = score;
}
}
}
return bestscore;
}
}
- Html5
最近 html5 滿紅的,抓來的code原本是 python 寫的,想來改寫成 javascript,順便熟悉一下html5的新功能!
// Robin Chiu : hcchiu63@gmail.com
// 2014/06/20 the first version
var context;
var width, height;
var output = output || {};
output.paintBoard = function(){
var board = document.getElementById('board');
width = board.width;
height = board.height;
context = board.getContext('2d');
context.clearRect (0, 0, width , height);
context.beginPath();
context.strokeStyle = '#000';
context.lineWidth = 4;
context.moveTo((width / 3), 0);
context.lineTo((width / 3), height);
context.moveTo((width / 3) * 2, 0);
context.lineTo((width / 3) * 2, height);
context.moveTo(0, (height / 3));
context.lineTo(width, (height / 3));
context.moveTo(0, (height / 3) * 2);
context.lineTo(width, (height / 3) * 2);
context.stroke();
context.closePath();
}
output.paintAIinfo = function() {
var moveInfo = document.getElementById('moveInfo');
moveInfo.innerHTML = '<h3> AI result:</h3>' +
'<br />Time:' + (AI.nowTime-AI.initTime) + 'ms' +
'<br />Calculate number:' + AI.number;
}
output.paintX = function(x, y) {
context.beginPath();
context.strokeStyle = '#ff0000';
context.lineWidth = 4;
var offsetX = (width / 3) * 0.1;
var offsetY = (height / 3) * 0.1;
var beginX = x * (width / 3) + offsetX;
var beginY = y * (height / 3) + offsetY;
var endX = (x + 1) * (width / 3) - offsetX * 2;
var endY = (y + 1) * (height / 3) - offsetY * 2;
context.moveTo(beginX, beginY);
context.lineTo(endX, endY);
context.moveTo(beginX, endY);
context.lineTo(endX, beginY);
context.stroke();
context.closePath();
}
output.paintO = function(x, y) {
context.beginPath();
context.strokeStyle = '#0000ff';
context.lineWidth = 4;
var offsetX = (width / 3) * 0.1;
var offsetY = (height / 3) * 0.1;
var beginX = x * (width / 3) + offsetX;
var beginY = y * (height / 3) + offsetY;
var endX = (x + 1) * (width / 3) - offsetX * 2;
var endY = (y + 1) * (height / 3) - offsetY * 2;
context.arc(beginX + ((endX - beginX) / 2), beginY + ((endY - beginY) / 2), (endX - beginX) / 2 , 0, Math.PI * 2, true);
context.stroke();
context.closePath();
}
var game = game || {};
// game init function
game.init = function() {
game.board = ['-','-','-','-','-','-','-','-','-'];
game.moves = [];
game.winner = "-";
}
// print the current status of game
game.print = function() {
// print the board
output.paintBoard();
// print the O and X
for(var i=0; i<9; i++) {
var x = i%3;
var y = Math.floor(i/3);
if(game.board[i]=="O")
output.paintO(x, y);
else if(game.board[i]=="X")
output.paintX(x, y);
}
// AI information
output.paintAIinfo();
// console debug
for(var i=0; i<9; i=i+3) {
console.info(game.board[i] + ", " + game.board[i+1] + ", " + game.board[i+2]);
}
}
// get the next move array
game.get_move = function() {
var empty = [];
for(var i=0; i<game.board.length; i++) {
if(game.board[i] == "-")
empty.push(i);
}
return empty;
}
// put some item to the position
game.move = function(marker, pos) {
game.board[pos] = marker;
game.moves.push(pos);
}
game.is_gameover = function() {
var win_position = [[0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [2,4,6]];
// check the patten
for(var i=0; i<win_position.length; i++) {
var p = win_position[i];
if(game.board[p[0]]==game.board[p[1]] && game.board[p[0]]==game.board[p[2]] && game.board[p[0]] != '-') {
game.winner = game.board[p[0]];
return true;
}
}
// check the if it's full
for(var i=0; i<game.board.length; i++) {
if(game.board[i] == "-") {
game.winner = "-";
return false;
}
}
return true;
}
// revert last move
game.revert = function() {
var pos = game.moves.pop();
game.board[pos] = '-';
game.winner = "-";
}
var AI = AI || {};
AI.init = function(marker) {
AI.marker = marker;
AI.initTime = 0;
AI.nowTime = 0;
AI.number = 0;
AI.type = "C";
if(AI.marker == "X")
AI.opponentmarker = "O";
else
AI.opponentmarker = "X";
}
AI.move = function(gameinstance) {
AI.initTime = new Date().getTime();
AI.number = 0;
var move = AI.rootsearch(gameinstance, 1);
AI.nowTime = new Date().getTime();
//var move_position = 4;
gameinstance.move(AI.marker, move);
}
AI.get_score = function(gameinstance) {
AI.number++;
if(gameinstance.is_gameover()) {
if(gameinstance.winner == AI.marker)
return 1;
else if(gameinstance.winner == AI.opponentmarker)
return -1;
}
return 0;
}
AI.rootsearch = function(gameinstance, color) {
var bestmove = -999;
var bestscore = -999;
// get the possiable move
var moves = gameinstance.get_move();
var candidate = [];
for(var i=0; i<moves.length; i++) {
// set the move
if(color == 1)
gameinstance.move(AI.marker, moves[i]);
else
gameinstance.move(AI.opponentmarker, moves[i]);
// get the score
var score = -1 * AI.negamax(gameinstance, -color);
// revert the move
gameinstance.revert();
// get the max score
if(score > bestscore) {
bestscore = score;
bestmove = moves[i];
candidate=[];
candidate.push(moves[i])
} else if(score == bestscore) {
candidate.push(moves[i]);
}
}
bestmove = candidate[Math.floor(Math.random()*candidate.length)];
return bestmove;
}
AI.negamax = function(gameinstance, color) {
var bestscore = -999;
//get the score
if(gameinstance.is_gameover()) {
return color * AI.get_score(gameinstance);
} else {
// get the possiable move
var moves = gameinstance.get_move();
for(var i=0; i<moves.length; i++) {
// set the move
if(color == 1)
gameinstance.move(AI.marker, moves[i]);
else
gameinstance.move(AI.opponentmarker, moves[i]);
// get the score
var score = -1 * AI.negamax(gameinstance, -color);
// revert the move
gameinstance.revert();
// get the max score
if(score > bestscore) {
bestscore = score;
}
}
return bestscore;
}
}
var Human = Human || {};
Human.init = function(marker) {
Human.marker = marker;
Human.type = 'H';
}
Human.move = function(gameinstance, pos) {
gameinstance.move(Human.marker, pos);
}
function clickHandler(e) {
var y = Math.floor(e.clientY / (height / 3));
var x = Math.floor(e.clientX / (width/ 3));
var pos = x + ( y * 3 );
if(game.board[pos]!="-") {
alert("invalid position...");
return;
}
// Human move
Human.move(game, pos);
game.print();
check();
// AI move
AI.move(game);
game.print();
check();
}
function check() {
if(game.is_gameover()) {
if(game.winner == AI.marker)
alert("AI win...");
else if(game.winner == Human.marker)
alert("You win...");
else
alert("Nobody win...");
game.init();
game.print();
}
}
function Play() {
game.init();
game.print();
Human.init("X");
AI.init("O");
}
- Android
有時間再將 html5 改寫成 Android,說實在Android上面的 TicTacToe 實在太多了,但是我下載了幾個,玩起來總覺得沒有畫在紙上好玩的感覺,後來覺得是在紙上畫上格子玩,把整張紙畫的滿滿的趣味不見了,所以Android的方向就改成接近原本在紙上的玩法!
想玩的話,可以上google play download,網址如下: