碁盤
ActionScript Igo, 20101010, naraba
人同士の対局のみ可能、思考ルーチンなし
劣化版 日本ルール (セキが認識できない、地の計算が不正確)
参考:
http://homepage1.nifty.com/Ike/katsunari/
/**
* Copyright naraba ( http://wonderfl.net/user/naraba )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/deMH
*/
/**
* ActionScript Igo, 20101010, naraba
* 人同士の対局のみ可能、思考ルーチンなし
* 劣化版 日本ルール (セキが認識できない、地の計算が不正確)
*
* 参考:
* http://homepage1.nifty.com/Ike/katsunari/
*/
package
{
import adobe.utils.CustomActions;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextFormat;
import com.bit101.components.Style;
import com.bit101.components.Label;
import com.bit101.components.PushButton;
import com.bit101.components.Panel;
import com.bit101.components.NumericStepper;
import com.bit101.components.Text;
[SWF(width="465", height="465", frameRate="10")]
public class AsIgo extends Sprite
{
private const STONE_EMPTY:int = 0;
private const STONE_BLACK:int = 1;
private const STONE_WHITE:int = 2;
private const STONE_OUTER:int = 4;
private const STONE_MIXED:int = STONE_BLACK | STONE_WHITE;
private const STONE_SCORED:int = 32;
private const MARK_FREE:int = -1;
private const MARK_EMPTY:int = STONE_EMPTY;
private const MARK_BLACK:int = STONE_BLACK;
private const MARK_WHITE:int = STONE_WHITE;
private const TERRITORY_BLACK:int = STONE_BLACK;
private const TERRITORY_WHITE:int = STONE_WHITE;
private const TERRITORY_NEUTRAL:int = STONE_MIXED;
private const TERRITORY_SCORED:int = STONE_SCORED;
private const STATUS_OVER:int = 0;
private const STATUS_PLAY:int = 1;
private const STATUS_EDIT:int = 2; // 未実装
private const BI_PASS:int = -1;
private var size:int;
private var side:int;
private var boardSize:int;
private var biN:int;
private var biE:int;
private var biS:int;
private var biW:int;
private var status:int;
private var board:Array;
private var markBoard:Array;
private var agehama:Array;
private var emptyList:Array;
private var numMove:int;
private var koBi:int;
private var koMove:int;
private var passes:int;
private var komi:Number;
private var prevMove:int;
// 画面描画用
private const VER_STRING:String = "ActionScript Igo, 20101010, naraba";
private const DR_BOARD_WIDTH:int = 300;
private const DR_BOARD_LEFT:int = 10;
private const DR_BOARD_TOP:int = 10;
private const DR_COLORS:Array = [0xEEEEEE, 0x000000, 0xFFFFFF, 0x333333];
private const DR_STAR_RAD:Number = 2;
private const DR_STAR_POS:Array = [[36, 40, 60, 80, 84], [64, 70, 112, 154, 160],
[88, 94, 100, 214, 220, 226, 340, 346, 352]];
private const DR_UI_LEFT:int = 325;
private const DR_UI_TOP:int = 70;
private var drCell:Number;
private var bcanvas:Sprite;
private var text:Text;
public function AsIgo():void {
bcanvas = new Sprite();
addChild(bcanvas);
bcanvas.x = DR_BOARD_LEFT;
bcanvas.y = DR_BOARD_TOP;
bcanvas.addEventListener(MouseEvent.CLICK, onBoardClick);
createUI();
initGame();
}
private function nm2c(n:int):int { return n % 2 == 1 ? STONE_BLACK : STONE_WHITE; }
private function bi2x(bi:int):int { return bi % side; }
private function bi2y(bi:int):int { return int(bi / side); }
private function xy2bi(x:int, y:int):int { return y * side + x; }
private function initGame():void {
initBoard();
initMarkBoard();
initRen();
agehama = [0, 0, 0];
numMove = 0;
koBi = BI_PASS;
koMove = -1;
passes = 0;
prevMove = BI_PASS;
status = STATUS_PLAY;
drawBoard();
}
private function initBoard():void {
side = size + 2;
boardSize = side * side;
board = new Array(boardSize);
biN = -side;
biE = 1;
biS = side;
biW = -1;
drCell = DR_BOARD_WIDTH / side;
emptyList = new Array();
var x:int, y:int;
for (var i:int = 0; i < boardSize; i++) {
x = bi2x(i);
y = bi2y(i);
if (x == 0 || x == side - 1 || y == 0 || y == side - 1) {
board[i] = STONE_OUTER;
} else {
board[i] = STONE_EMPTY;
emptyList.push(i);
}
}
}
private function initMarkBoard():void {
markBoard = new Array(boardSize);
clearMarkBoard();
}
private function clearMarkBoard():void {
for (var i:int = 0; i < boardSize; i++) {
markBoard[i] = MARK_FREE;
}
}
private function markTerritory(bi:int, ter:Array):int {
if (markBoard[bi] != MARK_FREE) {
if (board[bi] == STONE_BLACK || board[bi] == STONE_WHITE) return board[bi];
return STONE_SCORED;
}
if (board[bi] != STONE_EMPTY) {
markBoard[bi] = MARK_EMPTY;
return board[bi];
}
markBoard[bi] = MARK_EMPTY;
ter.push(bi);
var cn:int = markTerritory(bi + biN, ter);
var ce:int = markTerritory(bi + biE, ter);
var cs:int = markTerritory(bi + biS, ter);
var cw:int = markTerritory(bi + biW, ter);
var col:int = cn | ce | cs | cw;
if ((col & STONE_MIXED) == STONE_MIXED) return TERRITORY_NEUTRAL;
if ((col & STONE_BLACK) == STONE_BLACK) return TERRITORY_BLACK;
if ((col & STONE_WHITE) == STONE_WHITE) return TERRITORY_WHITE;
return TERRITORY_SCORED;
}
private function initRen():void {
Ren.init(side);
}
private function updateRen(c:int, bi:int):int {
var opp:int = c ^ STONE_MIXED;
var adjs:Array = new Array();
var opps:Array = new Array();
switch (board[bi + biN]) {
case c: adjs.push(bi + biN); break;
case opp: opps.push(bi + biN); break;
}
switch (board[bi + biE]) {
case c: adjs.push(bi + biE); break;
case opp: opps.push(bi + biE); break;
}
switch (board[bi + biS]) {
case c: adjs.push(bi + biS); break;
case opp: opps.push(bi + biS); break;
}
switch (board[bi + biW]) {
case c: adjs.push(bi + biW); break;
case opp: opps.push(bi + biW); break;
}
if (adjs.length == 0) {
Ren.create(c, bi);
} else {
var parId:int = Ren.rboard[adjs[0]];
Ren.join(parId, bi);
var sibId:int;
for (var i:int = 1; i < adjs.length; i++) {
sibId = Ren.rboard[adjs[i]];
if (sibId == parId) continue;
Ren.merge(parId, sibId);
Ren.headArray[sibId] = Ren.L_END;
}
}
return opps.length > 0 ? updateRenOpps(c, bi, opps) : 0;
}
private function updateRenOpps(c:int, bi:int, opps:Array):int {
var caps:int = 0;
var oppId:int;
var oppHead:int;
var next:int;
for (var i:int = 0; i < opps.length; i++) {
oppId = Ren.rboard[opps[i]];
if (oppId == Ren.EMPTY || Ren.colorArray[oppId] == Ren.EMPTY) continue;
oppHead = Ren.headArray[oppId];
if ( !isAlive(oppHead) ) {
for (var dbi:int = oppHead; dbi != Ren.L_END; dbi = next) {
emptyList.push(dbi);
board[dbi] = STONE_EMPTY;
Ren.rboard[dbi] = Ren.EMPTY;
next = Ren.rlists[dbi];
Ren.rlists[dbi] = Ren.L_END;
}
Ren.colorArray[oppId] = Ren.EMPTY;
caps += Ren.sizeArray[oppId];
koBi = opps[i];
}
}
return caps;
}
private function isAlive(head:int):Boolean {
for (var bi:int = head; bi != Ren.L_END; bi = Ren.rlists[bi]) {
if (board[bi + biN] == STONE_EMPTY || board[bi + biE] == STONE_EMPTY ||
board[bi + biS] == STONE_EMPTY || board[bi + biW] == STONE_EMPTY)
return true;
}
return false;
}
private function canCapture(c:int, bi:int):Boolean {
var id:int = Ren.rboard[bi];
if (id < 0) return false;
return (Ren.colorArray[id] == c && !isAlive(Ren.headArray[id]));
}
private function isSuicide(c:int, bi:int):Boolean {
var opp:int = c ^ STONE_MIXED;
board[bi] = c;
if ((board[bi + biN] == opp && !isAlive(Ren.headArray[Ren.rboard[bi + biN]])) ||
(board[bi + biE] == opp && !isAlive(Ren.headArray[Ren.rboard[bi + biE]])) ||
(board[bi + biS] == opp && !isAlive(Ren.headArray[Ren.rboard[bi + biS]])) ||
(board[bi + biW] == opp && !isAlive(Ren.headArray[Ren.rboard[bi + biW]])) ) {
board[bi] = STONE_EMPTY;
return false;
}
if ((board[bi + biN] == STONE_EMPTY ||
board[bi + biN] == c && isAlive(Ren.headArray[Ren.rboard[bi + biN]])) ||
(board[bi + biE] == STONE_EMPTY ||
board[bi + biE] == c && isAlive(Ren.headArray[Ren.rboard[bi + biE]])) ||
(board[bi + biS] == STONE_EMPTY ||
board[bi + biS] == c && isAlive(Ren.headArray[Ren.rboard[bi + biS]])) ||
(board[bi + biW] == STONE_EMPTY ||
board[bi + biW] == c && isAlive(Ren.headArray[Ren.rboard[bi + biW]])) ) {
board[bi] = STONE_EMPTY;
return false;
}
board[bi] = STONE_EMPTY;
return true;
}
private function isLegal(c:int, bi:int):Boolean {
if (board[bi] != STONE_EMPTY) return false;
if (koBi == bi && koMove == numMove) return false;
if (isSuicide(c, bi)) return false;
return true;
}
private function computeScore():Number {
var bs:Number = 0;
var ws:Number = 0;
var col:int;
var ter:Array;
var flag:Boolean = false;
clearMarkBoard();
for (var i:int = 0; i < emptyList.length; i++) {
if (markBoard[emptyList[i]] != MARK_FREE) continue;
ter = new Array();
col = markTerritory(emptyList[i], ter);
if (col == TERRITORY_BLACK) bs += ter.length;
else if (col == TERRITORY_WHITE) ws += ter.length;
else flag = true;
}
var score:Number = (bs + agehama[STONE_BLACK]) - (ws + agehama[STONE_WHITE]) - komi;
inform("対局終了 (" + (numMove + 1) + "手)\n", true);
if (score == 0) inform("結果 持碁");
else inform("結果 " + (score > 0 ? "黒" : "白") + Math.abs(score) + "目勝ち");
inform("------------------------------------------------------------------");
inform(" 地 黒: " + bs + "目、 白: " + ws + "目");
if (flag) inform(" ※所属が確定しない、または算出できない地点があります");
informHama();
return score;
}
private function playByMan(c:int, bi:int):void {
if (status != STATUS_PLAY) return;
if ( !play(c, bi) ) {
inform("着手できない点です");
return;
} else if (status == STATUS_PLAY) {
inform("次は" + (numMove + 1) + "手目: " + (nm2c(numMove + 1) == STONE_BLACK ? "黒" : "白") + "番\n", true);
}
if (status != STATUS_OVER) informHama();
}
private function play(c:int, bi:int):Boolean {
prevMove = BI_PASS;
if (bi == BI_PASS) {
passes++;
if (passes >= 2) {
status = STATUS_OVER;
computeScore();
}
numMove++;
drawBoard();
return true;
}
if (isLegal(c, bi)) {
numMove++;
board[bi] = c;
emptyList.splice(emptyList.indexOf(bi), 1);
var cap:int = updateRen(c, bi);
if (cap == 1 &&
board[bi + biN] != c && board[bi + biE] != c && board[bi + biS] != c && board[bi + biW] != c) {
koMove = numMove;
}
agehama[c] += cap;
passes = 0;
prevMove = bi;
drawBoard();
return true;
}
return false;
}
private function drawBoard():void {
var offset:Number = drCell / 2;
bcanvas.graphics.beginFill(0xEEEEEE);
bcanvas.graphics.drawRect(0, 0, side * drCell, side * drCell);
bcanvas.graphics.endFill();
bcanvas.graphics.lineStyle(1, 0);
for (var i:int = 0; i < size; i++) {
bcanvas.graphics.moveTo((i + 1) * drCell + offset, drCell + offset);
bcanvas.graphics.lineTo((i + 1) * drCell + offset, size * drCell + offset);
bcanvas.graphics.moveTo(drCell + offset, (i + 1) * drCell + offset);
bcanvas.graphics.lineTo(size * drCell + offset, (i + 1) * drCell + offset);
}
if (size == 9 || size == 13 || size == 19) {
var si:int = size / 4 - 2;
var bi:int;
bcanvas.graphics.beginFill(0x000000);
for (var st:int = 0; st < DR_STAR_POS[si].length; st++) {
bi = DR_STAR_POS[si][st];
bcanvas.graphics.drawCircle(bi2x(bi) * drCell + offset, bi2y(bi) * drCell + offset, DR_STAR_RAD);
}
bcanvas.graphics.endFill();
}
bcanvas.graphics.lineStyle(0, 0);
for (var j:int = 0; j < boardSize; j++) {
switch (board[j]) {
case STONE_BLACK:
case STONE_WHITE:
bcanvas.graphics.beginFill(DR_COLORS[board[j]]);
bcanvas.graphics.drawCircle(bi2x(j) * drCell + offset,
bi2y(j) * drCell + offset,
drCell / 2.1);
bcanvas.graphics.endFill();
if (j == prevMove) {
bcanvas.graphics.lineStyle(1.3, DR_COLORS[board[j] ^ STONE_MIXED]);
bcanvas.graphics.drawCircle(bi2x(j) * drCell + offset,
bi2y(j) * drCell + offset,
drCell / 3.5);
bcanvas.graphics.lineStyle(1, 0);
}
break;
case STONE_OUTER:
default:
break;
}
}
}
private function createUI():void {
var logo:TextField = new TextField();
logo.x = DR_UI_LEFT;
logo.y = DR_BOARD_TOP;
logo.selectable = false;
logo.text = " As I go ";
logo.setTextFormat(new TextFormat("Arial", 30, 0xDEADBEEF, true, true, true));
logo.width = 120;
logo.height = 40;
addChild(logo);
Style.embedFonts = false;
Style.fontName = "PF Ronda Seven";
Style.fontSize = 15;
// ボタン20
var initBtn:PushButton = new PushButton(this, DR_UI_LEFT, DR_UI_TOP, "新規対局", onInitBtnClick);
var passBtn:PushButton = new PushButton(this, DR_UI_LEFT, DR_UI_TOP + 200, "パス", onPassBtnClick);
// 対局の設定
var configPanel:Panel = new Panel(this, DR_UI_LEFT + 5, DR_UI_TOP + 35);
configPanel.width = 110;
configPanel.height = 120;
// 碁盤
Style.fontSize = 15;
var sizeLabel:Label = new Label(configPanel, 5, 10, "碁盤(路)");
Style.fontSize = 13;
var sizeStep:NumericStepper = new NumericStepper(configPanel, 20, 30, onSizeStepChange);
size = sizeStep.value = 9;
sizeStep.width = 70;
sizeStep.step = 1;
sizeStep.minimum = 2;
sizeStep.maximum = 19;
// コミ
Style.fontSize = 15;
var komiLabel:Label = new Label(configPanel, 5, 70, "コミ(目)");
Style.fontSize = 13;
var komiStep:NumericStepper = new NumericStepper(configPanel, 20, 90, onKomiStepChange);
komi = komiStep.value = 6.5;
komiStep.width = 70;
komiStep.step = 0.5;
komiStep.minimum = -10.5;
komiStep.maximum = 10.5;
// 情報通知
Style.fontSize = 14;
text = new Text(this, DR_BOARD_LEFT, DR_BOARD_TOP + DR_BOARD_WIDTH + 10, null);
text.text = VER_STRING + "\n\n";
text.width = 430;
text.height = 130;
text.editable = false;
text.selectable = false;
}
private function onBoardClick(e:MouseEvent):void {
playByMan(nm2c(numMove + 1), xy2bi(int(e.localX / drCell), int(e.localY / drCell)));
}
private function onInitBtnClick(event:Event):void {
status = STATUS_OVER;
initGame();
inform("対局開始: " + size + "路盤、 コミ" + komi + "目\n", true);
inform("1手目: 黒番\n");
informHama();
}
private function onPassBtnClick(event:Event):void {
playByMan(nm2c(numMove + 1), BI_PASS);
}
private function onSizeStepChange(event:Event):void {
var stepper:NumericStepper = event.currentTarget as NumericStepper;
size = stepper.value;
}
private function onKomiStepChange(event:Event):void {
var stepper:NumericStepper = event.currentTarget as NumericStepper;
komi = stepper.value;
}
private function inform(msg:String, isReset:Boolean=false):void {
if (isReset) text.textField.text = msg + "\n";
else text.textField.appendText(msg + "\n");
}
private function informHama():void {
inform(" ハマ 黒: " + agehama[STONE_BLACK] + "個、 白: " + agehama[STONE_WHITE] + "個");
}
}
}
class Ren
{
public static const OUTER:int = -2;
public static const EMPTY:int = -1;
public static const L_END:int = -1;
public static const SOME_LIBS:int = 2;
public static var nextId:int;
public static var rboard:Array;
public static var rlists:Array;
public static var colorArray:Array;
public static var sizeArray:Array;
public static var headArray:Array;
public static var tailArray:Array;
public static var maxId:int;
private static var boardSide:int;
private static var boardSize:int;
public static function init(bside:int):void
{
boardSide = bside;
boardSize = boardSide * boardSide;
maxId = (bside - 1) * (bside - 1) / 2; // 多め
nextId = 0;
rboard = new Array(boardSize);
rlists = new Array(boardSize);
var x:int, y:int;
for (var i:int = 0; i < boardSize; i++) {
x = i % boardSide;
y = int(i / boardSide);
if (x == 0 || x == boardSide - 1 || y == 0 || y == boardSide - 1) {
rboard[i] = OUTER;
} else {
rboard[i] = EMPTY;
}
rlists[i] = L_END;
}
colorArray = new Array(maxId);
sizeArray = new Array(maxId);
headArray = new Array(maxId);
tailArray = new Array(maxId);
for (i = 0; i < maxId; i++) colorArray[i] = EMPTY;
}
public static function create(c:int, bi:int):void
{
var id:int = nextId;
rboard[bi] = id;
rlists[bi] = L_END;
colorArray[id] = c;
sizeArray[id] = 1;
tailArray[id] = headArray[id] = bi;
nextId++;
if (nextId >= maxId) {
for (nextId = 0; colorArray[nextId] != EMPTY; nextId++) ;
}
}
public static function join(rid:int, bi:int):void
{
rboard[bi] = rid;
rlists[tailArray[rid]] = bi;
rlists[bi] = L_END;
sizeArray[rid]++;
tailArray[rid] = bi;
}
public static function merge(rid1:int, rid2:int):void
{
for (var bi:int = headArray[rid2]; bi != L_END; bi = rlists[bi]) {
rboard[bi] = rid1;
}
sizeArray[rid1] += sizeArray[rid2];
rlists[tailArray[rid1]] = headArray[rid2];
tailArray[rid1] = tailArray[rid2];
headArray[rid2] = L_END;
colorArray[rid2] = EMPTY;
}
}