Created
January 24, 2017 13:37
-
-
Save bhugueney/261fb2c3d81d401e220bf6f45abb0ac5 to your computer and use it in GitHub Desktop.
minimal Goban
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Set; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
class Group{ | |
final boolean color; | |
final int[] stones; | |
Set<Integer> freedoms; | |
public Group(int pos, boolean color, int[] freedoms){ | |
this.color= color; | |
stones= new int[]{pos}; | |
this.freedoms = new HashSet<Integer>(); | |
for(int p : freedoms){ | |
this.freedoms.add(p); | |
} | |
} | |
public Group(Group g0, Group g1, int connection){ | |
color= g0.color;// g1.color must obviously be the same | |
stones= new int[g0.stones.length + g1.stones.length]; | |
System.arraycopy(g0.stones, 0, stones, 0, g0.stones.length); | |
System.arraycopy(g1.stones, 0, stones, g0.stones.length, g1.stones.length); | |
freedoms= new HashSet<Integer>(g0.freedoms); | |
freedoms.addAll(g1.freedoms); | |
freedoms.remove(connection); | |
} | |
public void removeFreedom(int p){ | |
freedoms.remove(p); | |
} | |
public void addFreedom(int p){ | |
freedoms.add(p); | |
} | |
boolean getColor(){ | |
return color; | |
} | |
int[] getStones(){ | |
return stones; | |
} | |
public boolean isAtari(){ | |
return freedoms.size()==1; | |
} | |
public boolean isDead(){ | |
return freedoms.isEmpty(); | |
} | |
} | |
public class Goban{ | |
int size; | |
Group[] board; | |
int[] scores= new int []{0,0}; | |
int[][] neighbors; | |
static int[] deltas={-1,1}; | |
private static boolean isValid(int r, int c, int size){ | |
return (r>=0) && (c >=0) && (r < size) && (c < size); | |
} | |
private static int[] fromList(List<Integer> al){ | |
int[] res= new int[al.size()]; | |
for(int i=0; i != res.length; ++i){ | |
res[i]= al.get(i); | |
} | |
return res; | |
} | |
public Goban(int size){ | |
this.size= size; | |
board= new Group[size*size]; | |
neighbors= new int[board.length][]; | |
for(int r= 0, p=0; r != size; ++r){ | |
for(int c=0; c != size; ++c, ++p){ | |
List<Integer> currentN=new ArrayList<Integer>(); | |
for(int d : deltas){ | |
int rd= r+d; | |
int cd= c+d; | |
if(isValid(rd, c, size)){ | |
currentN.add(pos(rd,c)); | |
} | |
if(isValid(r, cd, size)){ | |
currentN.add(pos(r,cd)); | |
} | |
} | |
neighbors[p]= fromList(currentN); | |
} | |
} | |
} | |
private int pos(int r, int c){ | |
return r*size+c; | |
} | |
private int[] getNeighbors(int pos){ | |
return neighbors[pos]; | |
} | |
private int[] freedoms(int pos){ | |
ArrayList<Integer> res= new ArrayList<Integer>(); | |
for(int np : neighbors[pos]){ | |
if(board[np]==null){ | |
res.add(np); | |
} | |
} | |
return fromList(res); | |
} | |
private void empty(int pos){ | |
board[pos]= null; | |
for(int np : neighbors[pos]){ | |
if(board[np] != null){ | |
board[np].addFreedom(pos); | |
} | |
} | |
} | |
private void attacking(int pos, boolean color){ | |
for(int np : neighbors[pos]){ | |
if((board[np] != null) &&(board[np].getColor() != color)){ | |
if(board[np].isAtari()){ | |
int[] toRemove= board[np].getStones(); | |
for(int p : toRemove){ | |
empty(p); | |
} | |
scores[color?1:0]+= toRemove.length; | |
}else{ | |
board[np].removeFreedom(pos); | |
} | |
} | |
} | |
} | |
private Group grouping(int pos, boolean color){ | |
Group res= new Group(pos, color, freedoms(pos)); | |
for(int np : neighbors[pos]){ | |
if((board[np] != null) && (board[np].getColor() == color)){ | |
res = new Group(board[np], res, pos); | |
} | |
} | |
return res; | |
} | |
private boolean play(int pos, boolean color){ | |
if(board[pos] != null){ | |
return false; | |
} | |
attacking(pos, color); | |
Group newGroup= grouping(pos, color); | |
if(newGroup.isDead()){ | |
empty(pos); | |
return false; | |
}else{ | |
for(int p : newGroup.getStones()){ | |
board[p]= newGroup; | |
} | |
} | |
return true; | |
} | |
public boolean play(int r, int c, boolean color){ | |
return isValid(r,c, size) && play(pos(r,c), color); | |
} | |
public boolean isEmpty(int r, int c){ | |
return board[pos(r, c)]==null; | |
} | |
public boolean getColor(int r, int c){ | |
return board[pos(r,c)].getColor(); | |
} | |
public int getScore(boolean c){ | |
return scores[c?1:0]; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class GoGUI { | |
final static int SIZE= 8; | |
final static double EMPTY_RADIUS=0.1; | |
final static double STONE_RADIUS=0.45; | |
final static String PASS_STRING= "PASS"; | |
final static int PASS_X=0; | |
public static void main(String args[]){ | |
StdDraw.setXscale(-1, SIZE); | |
StdDraw.setYscale(-1, SIZE); | |
Goban goban= new Goban(SIZE); | |
boolean currentPlayer= false; | |
for(int consecutivePass= 0; consecutivePass != 2; currentPlayer= !currentPlayer){ | |
display(goban, currentPlayer); | |
boolean legalMove=false; | |
while(!legalMove){ | |
double[] xy= waitForClick(); | |
if(isPass(xy[0], xy[1])){ | |
++consecutivePass; | |
legalMove= true; | |
}else{ | |
int[] rc= toIntersection(xy); | |
if(rc != null){ | |
legalMove=goban.play(rc[0], rc[1], currentPlayer); | |
if(legalMove){ | |
consecutivePass= 0; | |
} | |
} | |
} | |
} | |
} | |
StdDraw.setPenColor(StdDraw.LIGHT_GRAY); | |
StdDraw.filledRectangle(SIZE/2., -1., SIZE/2.,0.75); | |
StdDraw.setPenColor(StdDraw.BLACK); | |
StdDraw.text(SIZE/2., -1, "Game over"); | |
displayScores(goban); | |
} | |
private static void displayScores(Goban g){ | |
StdDraw.setPenColor(StdDraw.BLACK); | |
StdDraw.textLeft(-1,-1,""+g.getScore(true)); | |
StdDraw.setPenColor(StdDraw.WHITE); | |
StdDraw.textLeft(0,-1,""+g.getScore(false)); | |
} | |
private static void display(Goban g, boolean currentPlayer){ | |
StdDraw.clear(StdDraw.LIGHT_GRAY); | |
StdDraw.setPenColor(StdDraw.BLACK); | |
for(int rc=0; rc!= SIZE; ++rc){ | |
StdDraw.line(0, rc, SIZE-1, rc); | |
StdDraw.line(rc, 0, rc, SIZE-1); | |
} | |
for(int r=0; r != SIZE; ++r){ | |
for(int c=0; c != SIZE; ++c){ | |
StdDraw.setPenColor((g.isEmpty(r,c) || g.getColor(r,c)) ? StdDraw.BLACK : StdDraw.WHITE); | |
StdDraw.filledCircle(r,c, g.isEmpty(r,c) ? EMPTY_RADIUS : STONE_RADIUS) ; | |
} | |
} | |
displayScores(g); | |
StdDraw.setPenColor(currentPlayer? StdDraw.BLACK : StdDraw.WHITE); | |
StdDraw.text(SIZE/2., -1, "Now Playing"); | |
StdDraw.text(SIZE-1, -1, "PASS"); | |
} | |
private static double[] waitForClick(){ | |
while(!StdDraw.mousePressed()){ | |
} | |
while(StdDraw.mousePressed()){ | |
} | |
return new double[]{StdDraw.mouseX(), StdDraw.mouseY()}; | |
} | |
private static boolean isPass(double x, double y){ | |
double dx= Math.round(x)-(SIZE-1); | |
double dy= Math.round(y)+1.; | |
return (dx*dx+dy*dy)<1.; | |
} | |
private static int[] toIntersection(double[] xy){ | |
double roundedX= Math.round(xy[0]); | |
double roundedY= Math.round(xy[1]); | |
double dX= xy[0] - roundedX; | |
double dY= xy[1] - roundedY; | |
return ((dX*dX + dY*dY)<STONE_RADIUS) ? new int[]{(int)roundedX, (int)roundedY} : null; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment