📄 game.java
字号:
else
return ss.readStone(row,col);
}
public void addSGFproperty (String property, Vector values) {
if (unhandledSGFproperties == null)
unhandledSGFproperties = new Vector(10);
Vector pair = new Vector(2);
pair.addElement(property);
pair.addElement(values);
unhandledSGFproperties.addElement(pair);
}
/**
* Place a move on the board unless it's an illegal move.
*
* THIS COMMENT IS OUT OF DATE, AND LEAVES OUT A LOT OF DETAILS.
*
* The move is legal if:
* - no stone at position
* - AND position is not ko
* - AND the move captures a group
* OR
* the group resulting from the move has >= 1 liberty
* - so create a new, composite group:
* - find newly connected groups (must all abut new stone)
* - create a new group with liberties that are the union
* of all the groups' libs and the new stone's libs
* - remember the old groups that make up the composite so
* the groupArray can be updated.
* - if composite has >= 1 libs, then
* - place the stone,
* - update the board with composite group (delete old ones)
* - update liberties of composite group
* - update liberties of any enemy group touching the new stone
*
* Note: fromServer should only be true the FIRST time this move
* is placed. When it is re-placed (i.e. during browsing)
* fromServer should be false.
*
* Note: In case it's not obvious, the move being placed must have
* as its parent the move reflected by the current board position.
*
* Returns: legal-move-p
*/
// This is the big one. Responsibilities are
// i) check legality (if local move, and not already done)
// ii) link move to tree (if legal, and not already linked)
// iii) "commit" the move - some complications here.
// RemovalMove has a commit function, which amalgamates captured groups,
// *** NB no longer as of 0.25 AMB
// but other moves just inherit from default,
// which does nothing other than link(), and set the flag!
// Commission is defined as the computation of captured groups,
// etc., which is deferred until the CURRENT GAME POSITION is at the
// move, in the case of server games....
// however, interestingly, commission responsibilities are handled
// by isLegal().... therefore conditions on iii) are (if we are not
// browsing, + ???
// so, if a move is (can be) checked to be legal, it is committed in
// all but the fact of having the isCommitted flag set... which is
// the responsibility of us!!
// iv) update the arrays
// enter scoring mode
// - after the second/third PASS is placed
// - after the Score Locally menu is chosen (set scoringEntry)
// - after a GameResultMove is undone
//
// exit scoring mode
// - before the second/third PASS is undone
// - before the scoringEntry move is undone
// - before a GameResultMove is placed
ILMV isLegalResult = new ILMV();
public synchronized boolean placeMove (Move m, TerminalWindow term,
boolean browsing,
boolean fromServer) {
// Exit scoring mode BEFORE a GameResultMove is placed.
// See backup() for the opposite logic.
if (scoringMode() && m instanceof GameResultMove)
exitScoringMode();
isLegalResult.newGroups = null;
isLegalResult.opponentGroups = null;
isLegalResult.failureReason = null;
boolean isNewMove = !m.isCommitted(); // save this till later...
// Moves from the server are assumed to be legal! If we're
// browsing and a server move is received, just skip this whole
// section of checking whether the move is legal or not, and just
// link the move without placing it. Later, when the user browses
// forward to this move it will be placed for real, computing
// captured groups, liberties, etc.
if (browsing && fromServer) {
m.link(); // Should be m.commit()?
return true;
}
else {
// If this move has already been "committed" then it has
// been placed before, so there's no need to check whether
// it's legal again.
SimpleGroupVector newGroups = null;
if (m.isCommitted()) {
newGroups = m.newGroups(this); // Need this below...
updateArrays(m, newGroups, groupsAbutting(m));
}
else {
if (m.isLegal(this, false, isLegalResult)) {
m.commit();
newGroups = isLegalResult.newGroups;
updateArrays(m, newGroups, isLegalResult.opponentGroups);
}
else if (isLegalResult.failureReason != null) {
String message = ("Illegal move: " + m + " "
+ isLegalResult.failureReason);
if (term == null)
Debug.println(message);
else
term.displayString(message);
return false;
}
}
// If we get here the move is definitely legal.
clearKo();
// Is the position just captured (if any) now ko?
// Yes, if exactly one stone was captured,
// && the capturing stone is a one-stone group with one liberty.
SimpleGroupVector capturedGroups = m.capturedGroups();
if ((capturedGroups != null
&& capturedGroups.size() == 1
&& capturedGroups.elementAt(0).size() == 1)
// The following depends on the fact that Game.captureGroup(),
// called above (in updateArrays()), has already updated
// Game.stoneArray.
&& (newGroups != null
&&newGroups.elementAt(0).size() == 1
&& newGroups.elementAt(0).numberOfLiberties() == 1))
setKoPosition(capturedGroups.elementAt(0).positionAt(0));
}
setCurrentMove(m);
if (scoringMode()) {
if (scoringLocally && scoringEntry == m.parent())
scoringEntryFollowedByNewMove = isNewMove;
scoreGame();
}
else if (m instanceof PassMove
&& m.parent() instanceof PassMove
&& (!isNetGame()
|| server.isNNGStype()
|| (server.isIGStype()
&& m.parent().parent() instanceof PassMove))) {
// Enter scoring mode after the second/third PASS is placed.
// See backup() for opposite logic.
enterScoringMode(false); // calls scoreGame()
}
m.noteMovePlaced(ss);
return true;
} // end method placeMove()
// Backup to the previous move.
// - If any groups were captured by this move they must be re-placed.
// - The liberties of the groups abutting this move must be incremented.
// - If the move before this one captured a ko, set the ko.
// - If this stone connected two groups they must be disconnected.
private synchronized void backup (boolean checkEnterExit) {
if (currentMove == root) {
//Debug.println("Error: Attempt to backup past the root move ignored.");
}
else {
Move savedCurrentMove = currentMove;
// exit scoring mode
// - BEFORE the second/third PASS is undone
// - BEFORE the scoringEntry move is undone
// check placeMove for opposite logic.
if (checkEnterExit && scoringMode()) {
if (currentMove == scoringEntry) {
exitScoringMode();
/* This attempted to remove variations that are just local scoring.
* Not sure this is a good idea in general, and it's broken anyway.
if (scoringLocally
&& currentMove.isVariation()
&& scoringEntryFollowedByNewMove)
scoringEntry.removeVariation(currentMove); // !wrong
*/
}
else if (currentMove instanceof PassMove
&& currentMove.parent() instanceof PassMove
&& (!isNetGame()
|| server.isNNGStype()
|| (server.isIGStype()
&& currentMove.parent().parent()
instanceof PassMove))) {
exitScoringMode();
}
}
// Locally undo the current move.
// Remove the placed positions from the board and from their
// respective groups and recompute the groups that this move
// touched. Since this move may have connected some groups
// it's necessary to recreate the separate groups that existed
// before this move was placed.
PositionVector positions = currentMove.placedPositions(size());
if (positions != null) { // various moves don't place any stones
for (int i = 0; i < positions.size(); i++) {
Position pos = positions.elementAt(i);
ss.writeStone(pos.row, pos.column, Move.EMPTY);
groupArray[pos.row][pos.column] = null;
SimpleGroupVector newGroups = new SimpleGroupVector(0, 4);
PositionVector abutters = positionsAbutting(pos);
if (abutters != null) {
for (int j = 0; j < abutters.size(); j++) {
Position p = abutters.elementAt(j);
SimpleGroup agroup = groupArray[p.row][p.column];
if (newGroups.indexOf(agroup) == -1) {
// The group at this abutting position has not already been
// recomputed. (It would have been if it were connected
// to another abutting position.)
if (agroup.color() != currentMove.color()) {
// The abutting group is a different color than the
// stone being removed so we can't be disconnecting
// the abutting group from any other group. Just
// add a liberty to the abutting group.
agroup.addLiberty(pos);
}
else {
// The abutting group is the same color, so we may be
// separating one group into two. Therefore we need to
// recompute the group at position p. Note that
// recomputeGroupAt() modifies groupArray[][].
// +++ Could add an optimization here. If the move
// being undone is black and there are < 2 black
// stones abutting it, then we're not separating a
// group into two (or more). Therefore just calling
// agroup.removePosition(p) would suffice here, and
// is much faster.
newGroups.addElement(recomputeGroupAt(p));
}
}
}
}
}
}
// Replace any groups that were captured by this move.
// Add liberties to the uncaptured groups.
// Remove liberties from the groups they touched.
SimpleGroupVector capturedGroups = currentMove.capturedGroups();
if (capturedGroups != null) {
for (int i = 0; i < capturedGroups.size(); i++) {
uncaptureGroup(capturedGroups.elementAt(i));
// If this group was captured by a StoneMove (rather than
// by a RemovalMove) then there is now a liberty on the
// group being uncaptured. Add it.
if (positions != null && positions.size() == 1)
capturedGroups.elementAt(i).addLiberty(positions.elementAt(0));
}
}
// Is the previous position (current.parent()) now ko?
// Yes, if it captured exactly one stone
// && it's a one-stone group with one liberty.
SimpleGroupVector pcgroups = currentMove.parent().capturedGroups();
PositionVector parentPositions
= currentMove.parent().placedPositions(getSize());
boolean koset = false;
if (currentMove.parent() != root
&& pcgroups != null
&& pcgroups.size() == 1
&& pcgroups.elementAt(0).size() == 1
&& parentPositions != null
&& parentPositions.size() == 1) {
Position pos = parentPositions.elementAt(0);
SimpleGroup parentGroup = groupAt(pos.row, pos.column);
if (parentGroup != null
&& parentGroup.size() == 1
&& parentGroup.numberOfLiberties() == 1) {
setKoPosition(pcgroups.elementAt(0).positionAt(0));
koset = true;
}
}
if (!koset) clearKo();
savedCurrentMove.noteMoveUndone(ss);
setCurrentMove(currentMove.parent()); // Update current move.
if (checkEnterExit) {
if (scoringMode())
scoreGame();
else if (savedCurrentMove instanceof GameResultMove)
// enter scoring mode AFTER a GameResultMove is undone
// check placeMove() for opposite logic.
enterScoringMode(false); // false = don't backupOnExit
}
}
} // end method backup()
/**
* Recompute the group at the given position using the paint fill
* algorithm. This side-effects the groupArray[][] only.
*/
private SimpleGroup recomputeGroupAt (Position pos) {
int[] xcoords = { -1, 1, 0, 0 };
int[] ycoords = { 0, 0, -1, 1 };
int mycolor = ss.readStone(pos.row, pos.column);
SimpleGroup group = new SimpleGroup(mycolor);
// +++ might want to pull these out as globals. They're kinda big.
PositionVector q = new PositionVector(20, 30);
PositionVector seen = new PositionVector(30, 40);
q.addElement(pos);
while (q.size() != 0) {
Position p = q.elementAt(0);
PositionVector libs = getLiberties(p);
int row = p.row;
int col = p.column;
group.addPosition(p);
for (int z = 0; z < libs.size(); z++)
group.addLiberty(libs.elementAt(z));
groupArray[row][col] = group;
q.removeElementAt(0);
seen.addElement(p);
for (int i = 0; i < 4; i++) {
int r = row - ycoords[i];
int c = col - xcoords[i];
if (r >= 0 && c >= 0 && r < size && c < size
&& ss.readStone(r, c) == mycolor
&& !seen.isMember(r, c)) {
q.addElement(new Position(r, c));
}
}
}
return group;
}
/*
* Backup (locally only) to a previous move.
* nmoves is the number of moves to go back.
* If nmoves = -1 then go to beginning.
*/
public void goBackward (int nmoves, boolean stopAtBranch) {
do {
backup(true);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -