Unityでオセロ①(http://akasuku.blog.jp/archives/66717359.html)の続きです。
今回は単純なAIを実装しました。
クラス設計は以下のようにしました。
前回の記事では黒番と白番の両方が人間でしたが、今回は人間とAIが交互に打ちます。
なので、PlayerBaseクラスを継承する形でHumanPlayerクラスとMachinePlayerクラスを作り、GameScriptクラスが持っていた「プレイヤーが石を置く」という役割をこれらのクラスに委譲しました。
さらに、これらのクラスからGridScriptクラスにアクセスする必要があったので、GameScriptクラスがBoardScriptクラスを保持する設計に変更し、PlayerBaseクラスからBoardScriptクラスへ関連を持たせました。
・PlayerBaseクラス
MachinePlayerクラスは各グリッドに石を置けるか否かを探索し、ラスタ順で最も最初のグリッドに石を置きます。
現状の実装だと、人間が黒石を押すと、その次の瞬間には白石が置かれてしまいます。
これの修正とAIを賢くするのが次の記事になるかと思います。
今回は単純なAIを実装しました。
クラス設計は以下のようにしました。
前回の記事では黒番と白番の両方が人間でしたが、今回は人間とAIが交互に打ちます。
なので、PlayerBaseクラスを継承する形でHumanPlayerクラスとMachinePlayerクラスを作り、GameScriptクラスが持っていた「プレイヤーが石を置く」という役割をこれらのクラスに委譲しました。
さらに、これらのクラスからGridScriptクラスにアクセスする必要があったので、GameScriptクラスがBoardScriptクラスを保持する設計に変更し、PlayerBaseクラスからBoardScriptクラスへ関連を持たせました。
・PlayerBaseクラス
using UnityEngine;・HumanPlayerクラス
using System.Collections;
abstract public class PlayerBase : MonoBehaviour
{
protected BoardScript boardScript_;
protected bool isBlack_;
public void SetBoardScript(BoardScript boardScript)
{
boardScript_ = boardScript;
}
public void IsBlack(bool isBlack)
{
isBlack_ = isBlack;
}
public abstract bool Play();
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
using UnityEngine;・MachinePlayerクラス
using System.Collections;
public class HumanPlayer : PlayerBase {
// タップを検知し、GridScriptを取得する
bool DetectTap(out GridScript gridScript)
{
gridScript = null;
if (Input.GetMouseButtonDown(0)) // タップ検知
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit = new RaycastHit();
if (Physics.Raycast(ray, out hit))
{
GameObject obj = hit.collider.gameObject; // タップしたGameObjectを取得
gridScript = obj.GetComponent<GridScript>(); // GridScriptを取得
return true;
}
}
return false;
}
public override bool Play()
{
// タップ検知
GridScript gridScript;
if (DetectTap(out gridScript))
{
// グリッドをタップしており、かつ、そこに石が無いなら
if (gridScript && !gridScript.GetStone())
{
// そこに石を置けるなら
if (gridScript.JudgeStonePutable(isBlack_))
{
// 石を置く
gridScript.PutStone(isBlack_);
// ひっくり返す
gridScript.TurnStone(isBlack_);
return true;
}
}
}
return false;
}
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
}
MachinePlayerクラスは各グリッドに石を置けるか否かを探索し、ラスタ順で最も最初のグリッドに石を置きます。
using UnityEngine;・GameScriptクラス
using System.Collections;
public class MachinePlayer : PlayerBase
{
void SearchPutableGrid(out bool[] isPutableGrid)
{
int colNum = boardScript_.GetColNum();
int rowNum = boardScript_.GetRowNum();
isPutableGrid = new bool[colNum * rowNum];
for (int r = 0; r < rowNum; ++r)
{
for (int c = 0; c < colNum; ++c)
{
GridScript gridScript = boardScript_.GetGrid(c, r).GetComponent<GridScript>();
if (!gridScript.GetStone() && gridScript.JudgeStonePutable(isBlack_))
{
isPutableGrid[r * colNum + c] = true;
}
else
{
isPutableGrid[r * colNum + c] = false;
}
}
}
}
bool SelectGrid(out GridScript gridScript)
{
bool[] isPutableGrid;
SearchPutableGrid(out isPutableGrid);
int colNum = boardScript_.GetColNum();
int rowNum = boardScript_.GetRowNum();
for (int r = 0; r < rowNum; ++r)
{
for (int c = 0; c < colNum; ++c)
{
int index = r * colNum + c;
if (isPutableGrid[index])
{
gridScript = boardScript_.GetGrid(c, r).GetComponent<GridScript>();
return true;
}
}
}
gridScript = null;
return false;
}
public override bool Play()
{
// 石を置くグリッドを選ぶ
GridScript gridScript;
SelectGrid(out gridScript);
if (!gridScript) Application.Quit();
// 石を置く
gridScript.PutStone(isBlack_);
// ひっくり返す
gridScript.TurnStone(isBlack_);
return true;
}
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
using UnityEngine;
using System.Collections;
public class GameScript : MonoBehaviour {
public GameObject gridPrefab_, blackStonePrefab_, whiteStonePrefab_;
BoardScript boardScript_;
PlayerBase[] players_;
bool isBlackTurn_;
// Use this for initialization
void Start () {
// BoardScriptクラスの生成
boardScript_ = new BoardScript();
boardScript_.SetGridPrefab(gridPrefab_);
boardScript_.SetBlackStonePrefab(blackStonePrefab_);
boardScript_.SetWhiteStonePrefab(whiteStonePrefab_);
boardScript_.MakeGrids();
// Playerクラスの生成
players_ = new PlayerBase[] {
new HumanPlayer(),
new MachinePlayer(),
};
players_[0].IsBlack(true);
players_[1].IsBlack(false);
for (int i = 0; i < 2; ++i)
{
players_[i].SetBoardScript(boardScript_);
}
isBlackTurn_ = true;
}
// Update is called once per frame
void Update () {
int playerNo = isBlackTurn_ ? 0 : 1;
if (players_[playerNo].Play())
{
isBlackTurn_ = !isBlackTurn_;
}
}
}
現状の実装だと、人間が黒石を押すと、その次の瞬間には白石が置かれてしまいます。
これの修正とAIを賢くするのが次の記事になるかと思います。