Make your own free website on Tripod.com
/* Connect 4 By Tantrum The board: 8!16 24 32 40 48 56 64! 7!15 23 31 39 47 55 63!71 ---+--------------------+--- 6!14 22 30 38 46 54 62!70 5!13 21 29 37 45 53 61!69 4!12 20 28 36 44 52 60!68 3!11 19 27 35 43 51 59!67 2!10 18 26 34 42 50 58!66 1! 9 17 25 33 41 49 57!65 ---+--------------------+--- 0! 8 16 24 32 40 48 56!64 a b c d e f g */ import java.awt.*; import java.applet.*; import java.io.*; public class Connect4 extends Applet { Thread cThread = null; int board[] = new int[72]; // playing board int rootBoard[] = new int[72]; // copy of playing board int next[] = new int[7]; // next free sq in column int game[] = new int[42]; // list of moves in the game int scores[] = new int[43]; // score at each move in the game int moveno; // move number int rootMoveno; int brdScore; // current positional score int rootBrdScore; int side; // side to move int rootSide; boolean win = false; // end of game, winner boolean auto = true; int bestval; int nodecnt; final static int win_score = -14000; final static int peakdepth = 11; int pv[][] = new int[peakdepth][peakdepth]; // principle variation int depth, maxdepth; int curCol = 0; // Current position of the next disk public void stop() { if (cThread != null) { cThread.stop(); cThread = null; } } void NewGame() { int i, j; for (i = 0; i < 72; ++i) board[i] = 3; for (i = 0; i < 7; ++i) { next[i] = i * 8 + 9; for (j = 0; j < 6; ++j) board[i * 8 + j + 9] = 0; } moveno = 0; brdScore = 0; side = 1; nodecnt = 0; bestval = 0; Copy(); } void Copy() { rootMoveno = moveno; rootSide = side; rootBrdScore = brdScore; for (int i = 0; i < 72; i++) rootBoard[i] = board[i]; } void Update(int i) { int v; scores[moveno] = brdScore; game[moveno] = i; board[next[i]] = side; v = UpdateScore(next[i]); brdScore = (v >= (-win_score)) ? -v : -(brdScore + v); ++moveno; ++next[i]; side = 3 - side; ++nodecnt; } void Dndate() { int col; --moveno; col = game[moveno]; --next[col]; board[next[col]] = 0; brdScore = scores[moveno]; side = 3 - side; } boolean yourMove(int m) { if ((m < 0) || (m > 6) || (board[next[m]] != 0)) { return false; } Update(m); Copy(); if (moveno >= 42 || brdScore <= win_score || brdScore >= (-win_score)) { win = true; repaint(); play(getCodeBase(), "gameover.au"); } else { repaint(curCol * xoff, 0, xoff, yoff * 7); // column play(getCodeBase(), "ding.au"); } return true; } Image notImage; // The image for white. Image crossImage; // The image for black. Image brdImage; // The board Image rdiskImage; // Players disk Image gdiskImage; // Players disk FileOutputStream fos; PrintStream ps; public void init() { notImage = getImage(getCodeBase(), "pieces/yellow.gif"); crossImage = getImage(getCodeBase(), "pieces/red.gif"); brdImage = getImage(getCodeBase(), "pieces/empty.gif"); rdiskImage = getImage(getCodeBase(), "pieces/ydisk.gif"); gdiskImage = getImage(getCodeBase(), "pieces/rdisk.gif"); setBackground(Color.lightGray); NewGame(); } final int xoff = 50; final int yoff = 50; public void update(Graphics g) { paint(g); } public void paint(Graphics g) { int i, j; Font font = new Font("Arial", Font.BOLD, 25); g.setFont(font); g.clearRect(0, 0, xoff * 7, yoff); if (rootBrdScore <= win_score || rootBrdScore >= (-win_score)) { if (win) g.drawString("You won this game.", 20, 20); else g.drawString("You lost this game.", 20, 20); } else if (rootMoveno >= 42) g.drawString("The game is a draw.", 20, 20); else if (rootSide == 1) g.drawImage(rdiskImage, curCol * xoff, 0, this); else g.drawImage(gdiskImage, curCol * xoff, 0, this); for (int r = 1; r < 7; r++) { for (int c = 0; c < 7; c++) { switch (rootBoard[c * 8 + (6 - r) + 9]) { case 0: g.drawImage(brdImage, c * xoff, r * yoff, this); break; case 1: g.drawImage(notImage, c * xoff, r * yoff, this); break; case 2: g.drawImage(crossImage, c * xoff, r * yoff, this); break; } } // for c } // for r } // the next disk over the board follows the mouse movement public boolean mouseMove(Event evt, int x, int y) { if (cThread != null) return true; int col = x / xoff; if (col <= 0) col = 0; if (col >= 6) col = 6; if (col != curCol) { curCol = col; repaint(0, 0, xoff * 7, yoff); } return true; } // the click of the mouse makes the move public boolean mouseDown(Event evt, int x, int y) { if (cThread != null) return true; // Click after game-over to start a new game if (moveno >= 42 || brdScore <= win_score || brdScore >= (-win_score)) { play(getCodeBase(), "ding.au"); NewGame(); repaint(); return true; } mouseMove(evt, x, y); if (yourMove(curCol)) { if (auto) { cThread = new ComputeThread(this); cThread.start(); } } else play(getCodeBase(), "error.au"); return true; } public boolean keyDown(Event event, int key) { if (key == Event.F2) { auto = !auto; if (auto) showStatus("Auto on"); else showStatus("Auto off"); return true; } if (key == Event.F3) { cThread = new ComputeThread(this); cThread.start(); return true; } return false; } public String getAppletInfo() { return "Connect Four by Mike Johnson"; } // compute a move void ComputeMove() { long starttime = System.currentTimeMillis(); Copy(); if (moveno >= 42 || brdScore <= win_score || brdScore >= (-win_score)) { win = true; return; } // Generate a random start move curCol = (int) (Math.random() * 7); if (curCol < 0) curCol = 0; if (curCol > 6) curCol = 6; pv[0][0] = curCol; // If its the first move then play the random move if (moveno >= 2) { depth = 0; nodecnt = 0; int i, j; for (i = 0; i < peakdepth; ++i) for (j = 0; j < peakdepth; ++j) pv[i][j] = 255; for (maxdepth = 2; maxdepth < peakdepth && moveno + maxdepth <= 44; ++maxdepth) { bestval = -alphabeta(-15000, 15000); showStatus("msec=" + (System.currentTimeMillis() - starttime) + ", depth=" + maxdepth + ", nodes=" + nodecnt + ", move=" + (char) (pv[0][0] + 'a')); if (System.currentTimeMillis() - starttime >= 1000) { break; } if (bestval <= win_score) { break; } if (bestval >= (-win_score)) { break; } } // for maxdepth } // if moveno curCol = pv[0][0]; Update(curCol); Copy(); win = false; if (moveno >= 42 || brdScore <= win_score || brdScore >= (-win_score)) { repaint(); // everyting play(getCodeBase(), "gameover.au"); } else { repaint(curCol * xoff, 0, xoff, yoff * 7); // column play(getCodeBase(), "ding.au"); } } int order[] = { 3, 2, 4, 1, 5, 0, 6 }; // move order int alphabeta(int alpha, int beta) { int i, v, mv, pmv = 0; if (brdScore <= win_score) return brdScore; if (depth >= maxdepth) return brdScore; if (moveno == 42) return 0; for (i = (-1); i < 7; i++) { /* select move, priciple variation first, rest after */ if (i == (-1)) { pmv = mv = pv[depth][depth]; if (mv == 255) continue; } else { mv = order[i]; if (mv == pmv) continue; } /* search move */ if (board[next[mv]] == 0) { Update(mv); ++depth; v = -alphabeta(-beta, -alpha); Dndate(); --depth; if (v > alpha) { alpha = v; pv[depth][depth] = mv; if ((depth == 0) && (curCol != mv)) { curCol = mv; repaint(0, 0, xoff * 7, yoff); // top line } for (v = depth + 1; v < maxdepth; ++v) pv[depth][v] = pv[depth + 1][v]; } if (alpha >= beta) return alpha; } } return alpha; } // Evaluation function int direction[] = { 1, 9, 8, 7 }; // would filling this square make a winning line ? boolean LineOfThree(int emptySq, int side) { if (board[emptySq] != 0) // only consider empty squares return false; for (int dirIndex = 0; dirIndex < 4; ++dirIndex) { // 4 line directions int sqCount = 0; int nearSq = emptySq - direction[dirIndex]; while (board[nearSq] == side) { ++sqCount; nearSq -= direction[dirIndex]; } nearSq = emptySq + direction[dirIndex]; while (board[nearSq] == side) { ++sqCount; nearSq += direction[dirIndex]; } if (sqCount >= 3) return true; } return false; } int valueOfCount[] = { 0, 1, 4, 10, 100 }; // points for partial rows int UpdateScore(int sq) { int d, k; int s1, s2; int i, j; int gap = 0; int val = 0; for (d = 0; d < 4; ++d) { // all 4 directions i = sq; for (k = 1; k <= 3; ++k) { // move down at most 3 squares i -= direction[d]; if (board[i] == 3) { // too far: just off edge i += direction[d]; break; } } for (;;) { // all rows of 4, in this direction, containing sq s1 = s2 = 0; for (k = 0, j = i; k < 4; ++k, j += direction[d]) { if (board[j] == 0) // empty square gap = j; else if (board[j] == side) // side just moved ++s2; else if (board[j] == (3 - side)) // side to move next ++s1; else if (board[j] == 3) // edge of board, so not possible to // be a line of 4 break; } // end for k, j if (k < 4 && board[j] == 3) // hit the other edge of board, so // no more lines of 4 break; if (s2 == 4) // won by side just moved return (-win_score) + (50 - moveno); else if (s1 == 0) { // row owned by s2, side just played, may be // an increase in ownership or a first if (s2 == 3) { // winning gap in line of 3 ? Depends on parity, // 1st side to play on odd squares, second on even if ((gap & 1) == (side & 1)) val += 50; else val += (valueOfCount[s2] - valueOfCount[s2 - 1]); // is it part of a gap pair if (LineOfThree(gap + 1, side) || LineOfThree(gap - 1, side)) val += 300; } else val += (valueOfCount[s2] - valueOfCount[s2 - 1]); } else if (s2 == 1) { // s1!=0 // was owned by s1, now not owned by either // gap-pairs cannot be destroyed if (s1 == 3 && (sq & 1) != (side & 1)) // parity block a // winning line of // three val += 50; else val += valueOfCount[s1]; } if (i == sq) break; i += direction[d]; } // end for all rows of 4 } // end for d, all 4 directions if (LineOfThree(sq + 1, 3 - side)) // next move is a win for the // opponent return -1000; return val; } // end update score } // end class connect4 class ComputeThread extends Thread { Connect4 a; public ComputeThread(Connect4 a) { this.a = a; } public void run() { a.ComputeMove(); a.cThread = null; } }