原創(chuàng)|使用教程|編輯:鄭恭琳|2018-01-17 10:38:16.000|閱讀 670 次
概述:本文講述如何使計(jì)算機(jī)模擬人類,學(xué)習(xí)如何使用算法和人工智能玩tic tac toe(井字游戲)。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
相關(guān)鏈接:
Tic tac toe是一個非常簡單的游戲,能夠讓你編程一臺電腦來玩。你可以編寫代碼告訴它如果可用的話進(jìn)入井字中心,看看對手是否有兩個連在一起,如果是這樣的話就封鎖它,或者如果有一個可用的就連接到一個角落,讓自己的兩個連在一起等等。
但是這不是你學(xué)會玩的方式。有人把網(wǎng)格放在你的面前,并開始把Xs和Os放在它上面。過了一會兒,你為自己想出了策略。
那么,我們?nèi)绾巫岆娔X模仿人類呢?計(jì)算機(jī)非常擅長的一件事是記住事情,為什么不創(chuàng)建一個應(yīng)用程序,讓電腦記住它是如何輸了一場井字游戲,然后避免再次做同樣的事情。
這將如何實(shí)現(xiàn)?首先,考慮游戲棋盤:它有九個單元格,每個單元格有三個狀態(tài):空,O和X。可以用一個九位數(shù)的三位數(shù)表示。所以,例如一塊空棋是000000000,中間有一個X(給出X的值為2)的棋是000020000等等。這個可以很容易地轉(zhuǎn)換成一個整數(shù),這個整數(shù)可以是散列表中的關(guān)鍵字。所以,當(dāng)電腦輸了這場游戲,它可以看看棋子是什么時候做了最后一步,評估,并設(shè)置一個hashmap(哈希映射)的值。將來在做一個動作之前,可以先看看棋盤的狀態(tài),如果它做了一個特定的動作,并且如果它出現(xiàn)在HashMap中,它會知道它上次輸過這場游戲, 所以這次應(yīng)該做點(diǎn)別的。
使用這種方法,不會有其他的策略,我們可以建立一個應(yīng)用程序,迅速學(xué)習(xí)如何玩井字游戲。不僅如此,當(dāng)你完成后,hashmap很容易轉(zhuǎn)移,即如何玩這個游戲的“記憶”可以給另一臺計(jì)算機(jī),然后它會立即知道如何玩這個游戲。這個算法太天真了,它只會在第一個可用空間中移動。起初,它會失去很多,但是隨著時間的推移,它將記錄失敗的地方,并遵循避免策略。你會發(fā)現(xiàn),它很快就學(xué)會了如何玩一個井字游戲,就像人類一樣。
以下是游戲的實(shí)際操作視頻——游戲中我拿X,電腦是O。它總是天真地走到第一個可用的位置,除非這個位置以前已經(jīng)不能用了。當(dāng)我在中心開始的時候,它總是往右走,我不斷地打擊電腦,直到它找出錯誤,然后迫使我陷入困境。當(dāng)我改變我的策略,電腦已經(jīng)學(xué)習(xí)到了:
實(shí)現(xiàn)這一機(jī)器學(xué)習(xí)的學(xué)習(xí)代碼是非常簡單的。這里有一個片段,顯示計(jì)算機(jī)評估棋子的位置,然后倒退導(dǎo)致丟失狀態(tài)的人為操作,將棋子狀態(tài)存儲在HashMap中:
public void learnFromLosing(){ int losingPosition = calcBoardValue(); losingPosition-= HUMAN_VALUE * Math.pow(3, lastHumanMove); losingGamePositions.put(losingPosition, true); } public int calcBoardValue(){ int boardValue = 0; for(int nIndex=0; nIndex<9; nIndex++){ boardValue += boardValues[nIndex] * Math.pow(3, nIndex); } return boardValue; }
boardValues[]數(shù)組只保留0、1、2為空、O和X,所以calcBoardValue通過在它們之間循環(huán)并將它們乘以它們的索引來將其轉(zhuǎn)換為整數(shù)——有效地將棋子轉(zhuǎn)換為整數(shù)。在learnFromLosing中,將最后一個人的移動的值從中減去,以使棋盤恢復(fù)到預(yù)失敗狀態(tài),然后失敗的位置存儲在loseGamePositions的哈希映射(hashmap)中。
當(dāng)輪到電腦移動時,它會循環(huán)通過棋盤,直到它找到一個空的位置(這是天真的部分!),然后調(diào)用isOKToMove,如果它返回true,將使計(jì)算機(jī)移動到該位置。
boolean computer_moved=false; for(int nIndex=0; nIndex<9; nIndex++){ if(boardValues[nIndex]==EMPTY_VALUE){ if(isOKToMove(nIndex)){ boardValues[nIndex]=COMPUTER_VALUE; computer_moved=true; totalMoves++; drawBoard(); break; } } }
然后isOKToMove函數(shù)會查看棋盤,如果計(jì)算機(jī)執(zhí)行此操作,并檢查該棋盤位置是否在失敗位置的hashmap中。如果是,那么就不能移動了。如果不是,那么電腦會做這個動作:
public boolean isOKToMove(int thisIndex){ int boardValue = calcBoardValue(); boardValue+=COMPUTER_VALUE * Math.pow(3, thisIndex); if(losingGamePositions.containsKey(boardValue)){ return false; } else { return true; } }
這就是它!為了您的方便,以下是實(shí)現(xiàn)此代碼的完整Android活動的源代碼(也就是您在上述視頻中看到的Android應(yīng)用程序)。
接下來的步驟和思考:
import android.content.DialogInterface; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import java.util.HashMap; public class MainActivity extends AppCompatActivity implements View.OnClickListener{ int[] buttonIDs = new int[] {R.id.btn1, R.id.btn2, R.id.btn3, R.id.btn4, R.id.btn5, R.id.btn6, R.id.btn7, R.id.btn8, R.id.btn9}; Button[] buttons = new Button[9]; int[] boardValues = new int[9]; int lastHumanMove=0; int totalMoves=0; public static final int EMPTY_VALUE=0; public static final int COMPUTER_VALUE=1; public static final int HUMAN_VALUE=2; public static final String COMPUTER_CHARACTER="O"; public static final String HUMAN_CHARACTER="X"; public static final String EMPTY_CHARACTER=""; public static final String NOBODY="NOBODY"; HashMaplosingGamePositions = new HashMap<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final Button tmpButton; for(int nIndex=0; nIndex<9; nIndex++) { buttons[nIndex] = (Button) findViewById(buttonIDs[nIndex]); buttons[nIndex].setOnClickListener(this); } drawBoard(); } @Override public void onClick(View v){ if(v instanceof Button){ Button thisButton = (Button) v; int index = Integer.parseInt(thisButton.getTag().toString()); if(boardValues[index]==EMPTY_VALUE){ boardValues[index]=HUMAN_VALUE; lastHumanMove=index; drawBoard(); totalMoves++; if(checkWinner(HUMAN_VALUE)){ learnFromLosing(); showWinner(HUMAN_CHARACTER); } else { if(totalMoves==9) { showWinner(NOBODY); } else { doComputerTurn(); } } } } } public void showWinner(String playerID){ AlertDialog alertDialog = new AlertDialog.Builder(MainActivity.this).create(); alertDialog.setTitle("Game Over"); if(playerID==NOBODY){ alertDialog.setMessage("It's a tie!"); } else { alertDialog.setMessage("The Winner is " + playerID); } alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); for(int nIndex=0; nIndex<9; nIndex++){ buttons[nIndex].setText(EMPTY_CHARACTER); boardValues[nIndex]=EMPTY_VALUE; totalMoves=0; } } }); alertDialog.show(); } public boolean checkWinner(int playerID){ if((boardValues[0]==playerID && boardValues[1]==playerID && boardValues[2]==playerID) || (boardValues[0]==playerID && boardValues[3]==playerID && boardValues[6]==playerID) || (boardValues[0]==playerID && boardValues[4]==playerID && boardValues[8]==playerID) || (boardValues[1]==playerID && boardValues[4]==playerID && boardValues[7]==playerID) || (boardValues[2]==playerID && boardValues[4]==playerID && boardValues[6]==playerID) || (boardValues[2]==playerID && boardValues[5]==playerID && boardValues[8]==playerID) || (boardValues[3]==playerID && boardValues[4]==playerID && boardValues[5]==playerID) || (boardValues[6]==playerID && boardValues[7]==playerID && boardValues[8]==playerID)) return true; else return false; } public void doComputerTurn(){ boolean computer_moved=false; for(int nIndex=0; nIndex<9; nIndex++){ if(boardValues[nIndex]==EMPTY_VALUE){ if(isOKToMove(nIndex)){ boardValues[nIndex]=COMPUTER_VALUE; computer_moved=true; totalMoves++; drawBoard(); break; } } } if (checkWinner(COMPUTER_VALUE)) { showWinner(COMPUTER_CHARACTER); } else { if(!computer_moved) { // There are no moves, so let's flag this as a bad board position learnFromLosing(); // Just do any move, and lose for(int nIndex=0; nIndex<9; nIndex++){ if(boardValues[nIndex]==EMPTY_VALUE){ boardValues[nIndex]=COMPUTER_VALUE; computer_moved=true; drawBoard(); break; } } } } } public boolean isOKToMove(int thisIndex){ int boardValue = calcBoardValue(); boardValue+=COMPUTER_VALUE * Math.pow(3, thisIndex); if(losingGamePositions.containsKey(boardValue)){ return false; } else { return true; } } public void learnFromLosing(){ int losingPosition = calcBoardValue(); losingPosition-= HUMAN_VALUE * Math.pow(3, lastHumanMove); losingGamePositions.put(losingPosition, true); } public int calcBoardValue(){ int boardValue = 0; for(int nIndex=0; nIndex<9; nIndex++){ boardValue += boardValues[nIndex] * Math.pow(3,nIndex); } return boardValue; } public void drawBoard(){ for(int nIndex=0; nIndex<9; nIndex++){ switch(boardValues[nIndex]){ case HUMAN_VALUE: buttons[nIndex].setText(HUMAN_CHARACTER); break; case COMPUTER_VALUE: buttons[nIndex].setText(COMPUTER_CHARACTER); break; default: buttons[nIndex].setText(EMPTY_CHARACTER); } } } }
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@fc6vip.cn