import java.awt.*; import java.awt.image.*; import java.net.*; import java.applet.*; class Connect4Kernel { public final static int EMPTY = 0; public final static int COMP = 1; public final static int HUMAN = -1; public final static int BORDER = 99; public final static int GAME_NOT_OVER = 0; public final static int ONE_HAS_WON = 1; public final static int BOARD_FULL = -1; public final static int UNDECIDED = 0; public final static int PLAYER_WINS = 1; public final static int PLAYER_LOOSES = -1; private int board[] = new int[156]; private int field[] = new int[13]; private int maxDepth; private Connect4 c4; private int cnt; public int Cnt() { return cnt; } // ------------------------------------------------------------------------ private boolean _IsEmpty(int column) { if(field[column]<114) return true; else return false; } // ------------------------------------------------------------------------ private int _DoMove(int player, int column) { int f = field[column]; board[f] = player; field[column] += 13; return f; } // ------------------------------------------------------------------------ private int _BestMove(int player, int move[], int depth) { int NO_MORE_SEARCH = 10; // final! int res = PLAYER_LOOSES; int rivals_move[] = new int[1]; int i; int curr_field, curr_column; int value[] = new int[1]; int max_value = -1; for(move[0]=-1, depth--, i=3, value[0]=0 ; imax_value) { move[0] = i; res = PLAYER_WINS; max_value = value[0]; } break; case UNDECIDED: if(res==PLAYER_LOOSES || (res==UNDECIDED && value[0]>max_value)) { move[0] = i; res = UNDECIDED; max_value = value[0]; } break; case PLAYER_WINS: // not a good move if(res==PLAYER_LOOSES && value[0]>max_value) { move[0] = i; max_value = value[0]; } break; } } else { if(res==PLAYER_LOOSES && value[0]>max_value) { move[0] = i; res = UNDECIDED; max_value = value[0]; } } break; } board[curr_field] = EMPTY; field[curr_column] -= 13; } if(depth==maxDepth) { StringBuffer str = new StringBuffer(30); str.append("Analyzing"); for(int j=3 ; j2 && _Column(i)<10 && _Line(i)>2 && _Line(i)<9) board[i] = EMPTY; else board[i] = BORDER; for(i=0 ; i<13 ; i++) field[i] = 3*13+i; } // ----------------------------------------- public boolean IsEmpty(int column) { if(column>=0 && column<7) return _IsEmpty(column+3); else return false; } // ----------------------------------------- public int GameOver(int f, int value[]) { int comp = board[f]; int rival; int i; int u, d, l, r, ul, ur, dl, dr; int cnt0, cnt1, cnt2, cnt3; int cnt0b, cnt1b, cnt2b, cnt3b; int f0a, f0b, f1a, f1b, f2a, f2b, f3a, f3b; int f0c, f0d, f1c, f1d, f2c, f2d, f3c, f3d; int tmp; cnt0 = cnt1 = cnt2 = cnt3 = 1; cnt0b = cnt1b = cnt2b = cnt3b = 0; u = d = l = r = ul = ur = dl = dr = f; f0a = f0b = f0c = f0d = f1a = f1b = f1c = f1d = f2a = f2b = f2c = f2d = f3a = f3b = f3c = f3d = 1; if(comp!=EMPTY && comp!=BORDER) { rival = board[f] == HUMAN ? COMP : HUMAN;; for(i=1 ; i<4 ; i++) { // horizontal if(f0c!=0) { if((tmp = board[++r]) != BORDER) if(f0a!=0) if(tmp==comp) cnt0++; else { f0a = 0; if(tmp==EMPTY) cnt0b++; else f0c = 0; } else if(tmp!=rival) cnt0b++; else f0c = 0; else f0c = 0; } if(f0d!=0) { if((tmp = board[--l]) != BORDER) if(f0b!=0) if(tmp==comp) cnt0++; else { f0b = 0; if(tmp==EMPTY) cnt0b++; else f0d = 0; } else if(tmp!=rival) cnt0b++; else f0d = 0; else f0d = 0; } // links unten -> rechts oben if(f1c!=0) { if((tmp = board[ur+=14]) != BORDER) if(f1a!=0) if(tmp==comp) cnt1++; else { f1a = 0; if(tmp==EMPTY) cnt1b++; else f1c = 0; } else if(tmp!=rival) cnt1b++; else f1c = 0; else f1c = 0; } if(f1d!=0) { if((tmp = board[dl-=14]) != BORDER) if(f1b!=0) if(tmp==comp) cnt1++; else { f1b = 0; if(tmp==EMPTY) cnt1b++; else f1d = 0; } else if(tmp!=rival) cnt1b++; else f1d = 0; else f1d = 0; } // links oben -> rechts unten if(f2c!=0) { if((tmp = board[ul+=12]) != BORDER) if(f2a!=0) if(tmp==comp) cnt2++; else { f2a = 0; if(tmp==EMPTY) cnt2b++; else f2c = 0; } else if(tmp!=rival) cnt2b++; else f2c = 0; else f2c = 0; } if(f2d!=0) { if((tmp = board[dr-=12]) != BORDER) if(f2b!=0) if(tmp==comp) cnt2++; else { f2b = 0; if(tmp==EMPTY) cnt2b++; else f2d = 0; } else if(tmp!=rival) cnt2b++; else f2d = 0; else f2d = 0; } // vertikal if(f3c!=0) { if((tmp = board[u+=13]) != BORDER) if(f3a!=0) if(tmp==comp) cnt3++; else { f3a = 0; if(tmp==EMPTY) cnt3b++; else f3c = 0; } else if(tmp!=rival) cnt3b++; else f3c = 0; else f3c = 0; } if(f3d!=0) { if((tmp = board[d-=13]) != BORDER) if(f3b!=0) if(tmp==comp) cnt3++; else { f3b = 0; if(tmp==EMPTY) cnt3b++; else f3d = 0; } else if(tmp!=rival) cnt3b++; else f3d = 0; else f3d = 0; } } // Wertigkeit des Zuges bestimmen value[0] = 0; if((cnt0b + cnt0)>3) value[0] = (cnt0-1)*6 + cnt0b; if((cnt1b + cnt1)>3) value[0] += (cnt1-1)*6 + cnt1b; if((cnt2b + cnt2)>3) value[0] += (cnt2-1)*6 + cnt2b; if((cnt3b + cnt3)>3) value[0] += (cnt3-1)*6 + cnt3b; if(cnt0>3 || cnt1>3 ||cnt2>3 ||cnt3>3) return ONE_HAS_WON; for(i=3 ; i<10 ; i++) if(field[i]<156) return GAME_NOT_OVER; return BOARD_FULL; } return GAME_NOT_OVER; } // ------------------------------------------------------------------------ public int DoMove(int player, int column) { int f = -1; if(IsEmpty(column)) f = _DoMove(player, column+3); return f; } // ------------------------------------------------------------------------ public int BestMove(int player, int move[], int depth) { maxDepth = depth-1; cnt = 0; int res = _BestMove(player, move, depth); move[0] -= 3; return res; } // ------------------------------------------------------------------------ public int Field(int l, int r) { return (l+3)*13+r+3; } // ------------------------------------------------------------------------ public int Column(int f) { return _Column(f) -3; } // ------------------------------------------------------------------------ public int Line(int f) { return _Line(f)-3; } // ------------------------------------------------------------------------ public char F(int l, int r) { switch(board[Field(l,r)]) { case EMPTY: return ' '; case COMP: return 'X'; default: return '0'; } } // ------------------------------------------------------------------------ public int FieldState(int l, int r) { return board[Field(l,r)]; } }; // =========================================================================== public class Connect4 extends Applet { // ------------------------------------------------------------------------ public final static int WAITING_FOR_MOVE = 0; public final static int ANALYZING = 1; public final static int RESTART = 2; public final static int BORDER_WIDTH = 5; // ------------------------------------------------------------------------ Connect4Kernel c4k; int currField; int level; int bestMove[] = new int[1]; int res = Connect4Kernel.UNDECIDED; int value[] = new int[1]; int changeCnt = 0; boolean levelEverDecreased = false; int lastCompArrowColumn = -1; int lastHumanArrowColumn = -1; boolean lastHumanArrowEmpty = false; int currFontSize = 0; Font font; StringBuffer currStatus; Graphics g = null; Panel panel = null; int mode = -1; // ------------------------------------------------------------------------ public void init() { c4k = new Connect4Kernel(this); bestMove[0] = c4k.Field(0,0); value[0] = 0; level = 4; levelEverDecreased = false; changeCnt = 0; lastHumanArrowColumn = -1; lastCompArrowColumn = -1; lastHumanArrowEmpty = true; currStatus = new StringBuffer("Start your game!"); if(g==null) g = getGraphics(); SetMode(WAITING_FOR_MOVE); } // ------------------------------------------------------------------------ public void SetMode(int m) { mode = m; } // ------------------------------------------------------------------------ public boolean IsMode(int m) { return mode==m; } // ------------------------------------------------------------------------ public void DrawStatus(String str) { Dimension d = size(); int dx = (d.width - 2*BORDER_WIDTH) / 7; int dy = d.height / 8; int i; Font f; // clear status display area g.setColor(Color.white); g.fillRect(BORDER_WIDTH, 7*dy+1, 7*dx, dy-BORDER_WIDTH-1); if(currFontSize!=dy) { currFontSize = dy; font = new Font("Dialog", 0, (dy*5)/10); } g.setFont(font); currStatus = new StringBuffer(str); g.setColor(Color.black); g.drawString(currStatus.toString(), BORDER_WIDTH + 10, 7*dy+(dy*5)/8); for(i=4 ; i<9 && i<=level ; i++) { g.setColor(Color.green); g.fillRect(7*dx-BORDER_WIDTH-20, 8*dy-BORDER_WIDTH-(i-3)*4-1, 22, 3); } } // ------------------------------------------------------------------------ public void DrawChip(int c, int r) { Color color; Dimension d = size(); int dx = (d.width - 2*BORDER_WIDTH) / 7; int dy = d.height / 8; int xk = BORDER_WIDTH + dx*c + dx/8; int yk = d.height - (r+2)*dy + dy/8; int xs = (dx*3)/4; int ys = (dy*3)/4; switch(c4k.FieldState(r, c)) { case Connect4Kernel.COMP: color = Color.green; break; case Connect4Kernel.HUMAN: color = Color.red; break; case Connect4Kernel.EMPTY: default: color = Color.white; break; } g.setColor(color); g.fillOval(xk, yk, xs, ys); // g.setColor(Color.black); // g.drawOval(xk, yk, xs, ys); } // ------------------------------------------------------------------------ public void paint(Graphics g) { Dimension d = size(); int dx = (d.width - 2*BORDER_WIDTH) / 7; int dy = d.height / 8; int r, c; // clear entire display area g.setColor(Color.white); g.fillRect(0, 0, 7*dx+2*BORDER_WIDTH-1, dy*8-1); g.setColor(Color.blue.darker().darker()); g.fillRect(BORDER_WIDTH, dy, dx*7, dy*6); for(r = 0 ; r < 6 ; r++) for(c = 0 ; c < 7 ; c++) DrawChip(c, r); DrawStatus(currStatus.toString()); g.setColor(Color.gray); for(r=0 ; r40000) { level--; changeCnt = 0; levelEverDecreased = true; } else changeCnt = 0; } // ------------------------------------------------------------------------ public boolean mouseUp(Event evt, int x, int y) { int r; if(IsMode(RESTART)) { init(); repaint(); return true; } SetMode(ANALYZING); game: if(c4k.GameOver(bestMove[0], value)==Connect4Kernel.GAME_NOT_OVER) { // get column by position of mouse click Dimension d = size(); r = ((x-BORDER_WIDTH) * 7) / d.width; if(r<0 || r>6) break game; if(c4k.IsEmpty(r)) { currField = DoMove(Connect4Kernel.HUMAN, r); switch(res = c4k.GameOver(currField, value)) { case Connect4Kernel.BOARD_FULL: DrawStatus("It ends in a tie! (click to restart)"); SetMode(RESTART); break; case Connect4Kernel.ONE_HAS_WON: DrawStatus("You won! (click to restart)"); SetMode(RESTART); break game; case Connect4Kernel.GAME_NOT_OVER: res = c4k.BestMove(Connect4Kernel.COMP, bestMove, level); currField = DoMove(Connect4Kernel.COMP, bestMove[0]); AdaptLevel(); switch(res) { case Connect4Kernel.PLAYER_LOOSES: // DrawStatus("I could loose!"); DrawStatus("Your move!"); break; case Connect4Kernel.PLAYER_WINS: if(c4k.GameOver(currField, value)!=Connect4Kernel.GAME_NOT_OVER) { DrawStatus("I won! (click to restart)"); SetMode(RESTART); break game; } else { if(levelEverDecreased) DrawStatus("Your move!"); else DrawStatus("I'm going to win!"); } break; case Connect4Kernel.UNDECIDED: default: DrawStatus("Your move!"); break; } break; } } // else // DrawStatus("Column occupied"); } if(IsMode(ANALYZING)) SetMode(WAITING_FOR_MOVE); return true; } // ------------------------------------------------------------------------ void DrawArrow(Graphics g, int dx, int dy, int c, Color color, boolean fill) { int xa[] = new int[8]; int ya[] = new int[8]; g.setColor(color); xa[0] = BORDER_WIDTH + c*dx+dx/2; ya[0] = (dy*3)/4; xa[1] = xa[0] + dx/5; ya[1] = ya[0] - dy/4; xa[2] = xa[0] + dx/10; ya[2] = ya[1]; xa[3] = xa[2]; ya[3] = ya[0] - dy/2; xa[4] = xa[0] - dx/10; ya[4] = ya[3]; xa[5] = xa[4]; ya[5] = ya[1]; xa[6] = xa[0] - dx/5; ya[6] = ya[1]; xa[7] = xa[0]; ya[7] = ya[0]; if(fill) g.fillPolygon(xa, ya, 8); else g.drawPolygon(xa, ya, 8); } // ------------------------------------------------------------------------ void DrawArrows(int column, int player) { if(column>=0 && column<7) { boolean empty = c4k.IsEmpty(column); Color color; Dimension d = size(); int dx = (d.width - 2*BORDER_WIDTH) / 7; int dy = d.height / 8; // clear arrow display area g.setColor(Color.white); g.fillRect(BORDER_WIDTH, BORDER_WIDTH, 7*dx, dy-BORDER_WIDTH); if(player==Connect4Kernel.HUMAN) { lastHumanArrowColumn = column; lastHumanArrowEmpty = empty; } else if(player==Connect4Kernel.COMP) lastCompArrowColumn = column; else { lastHumanArrowColumn = -1; lastCompArrowColumn = -1; } if(lastHumanArrowColumn!=-1) { if(lastHumanArrowEmpty) color = Color.red; else color = Color.gray; DrawArrow(g, dx, dy, lastHumanArrowColumn, color, true); } if(lastCompArrowColumn!=-1) { color = Color.green; DrawArrow(g, dx, dy, lastCompArrowColumn, color, lastCompArrowColumn!=lastHumanArrowColumn); } } } // ------------------------------------------------------------------------ public boolean mouseMove(Event evt, int x, int y) { if(IsMode(WAITING_FOR_MOVE)) { Dimension d = size(); int c; // examine column c = ((x-BORDER_WIDTH) * 7) / d.width; if(c!=lastHumanArrowColumn) DrawArrows(c, Connect4Kernel.HUMAN); } return true; } // ------------------------------------------------------------------------ public String getAppletInfo() { return "Connect4 - Java (beta) Applet by Sven Wiebus, Dec. 1995"; } }