Banner
{ Deutsch | English }
Othello

Othello − LX



Source Code


At first some CSS stuff. For this game I juggled with classes which contain different background images for table cells.
<style type="text/css"> table.spiel { border-color: black; border-collapse: collapse; background-color: #008000; } .spiel td { height:35px; width:35px; padding: 0px; font-weight: bold; border: 1px solid black; text-align:center; } .white { background: url(games/othello/lx/white.gif) center no-repeat; color:black; cursor:default; } .towhite { background: url(games/othello/lx/towhite.gif) center no-repeat; color:black; } .black { background: url(games/othello/lx/black.gif) center no-repeat; color:white; cursor:default; } .toblack { background: url(games/othello/lx/toblack.gif) center no-repeat; color:white; } .normal { cursor: auto; } .point { cursor: pointer; color:#A080FF; } td.point:hover { background: url(games/othello/lx/bw.gif) center no-repeat; } </style> <script type="text/javascript">
Now to the actual script. At first there are the usual declarations of global variables.
If it is the computer's turn, this variable is 1. It's used to block further actions from the player.
var progress = 1;
the colors of both parties
var computer = 1; var player = 2;
1 if the player wants to see the flip counts
var showHelp = 0;
the board, 0 is an empty cell, 1 is a white disc, 2 is a black disc
var Spielfeld = new Array(64); var tempSpielfeld = new Array(64);
contains possible legal moves
var possibilities = new Array(); var tempPossibilities = new Array();
A matrix of values to evaluate the worth of positions on the board for the A.I.
The edges are most important because they cannot be taken by the other player anymore. The fields next to the edges are the worst ones, because those usually lead to the other player getting the edge. The fields on the boarder have higher values than those in the middle, because the latter ones change their owner frequently.
var wert = new Array( 50, -1, 5, 2, 2, 5, -1, 50, -1, -10, 1, 1, 1, 1, -10, -1, 5, 1, 1, 1, 1, 1, 1, 5, 2, 1, 1, 0, 0, 1, 1, 2, 2, 1, 1, 0, 0, 1, 1, 2, 5, 1, 1, 1, 1, 1, 1, 5, -1, -10, 1, 1, 1, 1, -10, -1, 50, -1, 5, 2, 2, 5, -1, 50 );
the two variables to count the discs currently on the board
var white, black;
The following function initializes the game. It clears all discs from the board, resets the arrays of possible moves and places the 4 starting discs.
function init() { for (var i = 0; i < 64; i++) { putPiece(0,i); Spielfeld[i] = 0; possibilities[i] = new Array(); possibilities[i]['anzahl'] = 0; possibilities[i]['flips'] = ''; tempPossibilities[i] = new Array(); tempPossibilities[i]['anzahl'] = 0; tempPossibilities[i]['flips'] = ''; } putPiece(1, 27); putPiece(2, 28); putPiece(2, 35); putPiece(1, 36); }
The startgame() function is invoked when the player presses the New Game button. It calls init(), checks the current possibilities, hides the button, and either calls the A.I. if it plays black or shows the flipcount for the player (if he wished so).
function startgame() { init(); checkPossibilities(2); document.getElementById('nG').style.visibility = 'hidden'; if (computer == 2) KI(); else { progress = 0; for (var i = 0; i < 64; i++) if (possibilities[i]['anzahl'] > 0) putPiece (5,i); } }
The following function checks, if there are any possible moves at the moment.
function checkMove() { var j = 0; for (var i = 0; i < 64; i++) { j += possibilities[i]['anzahl']; } return j; }
put() places the player's disc and initiates the computer's move.
function put(field) {
Of course a move is only possible, if there is no disc currently on this field.
if (Spielfeld[field] != 0) return false;
Also the move has to be part of the possibilities[] array.
if (possibilities[field]['anzahl'] == 0) return false;
disallow further actions until the computer finished his move
progress = 1;
put the disc
putPiece(player, field);
flips the discs between the new placed disc and discs already on the board
flip(player, field);
clears the flipcounts
for (var i = 0; i < 64; i++) if (Spielfeld[i] != 1 && Spielfeld[i] != 2) putPiece(0,i);
Finally the new possibilities for the computer are checked.
checkPossibilities(computer);
If a move is possible, the A.I. is called.
if (checkMove() != 0) { setTimeout("KI()",2000); }
Otherwise the player can put another disc, if he has possible moves. If even that is not the case, the game is over.
else { progress = 0; checkPossibilities(player); if (checkMove() == 0) gameOver(); else for (i = 0; i < 64; i++) if (possibilities[i]['anzahl'] > 0) putPiece (5,i); } return true; }
If the game is over, there's some stuff written on the board and the New Game button is shown again.
function gameOver() { progress = 1; document.getElementById('O9').firstChild.nodeValue = 'G'; document.getElementById('O10').firstChild.nodeValue = 'A'; document.getElementById('O11').firstChild.nodeValue = 'M'; document.getElementById('O12').firstChild.nodeValue = 'E'; document.getElementById('O19').firstChild.nodeValue = 'O'; document.getElementById('O20').firstChild.nodeValue = 'V'; document.getElementById('O21').firstChild.nodeValue = 'E'; document.getElementById('O22').firstChild.nodeValue = 'R'; if (white > black) { . . . } else if (white < black) { . . . } else { . . . } document.getElementById('nG').style.visibility = 'visible'; }
flip() rotates discs
function flip(color, field) { var temp = new Array(); temp = possibilities[field]['flips'].split('|'); for (i = 0; i < temp.length-1; i++) { putPiece(color,temp[i]); } }
fakeFlip() does the same, except that it doesn't switch the graphics
function fakeFlip(color, field) { var temp = new Array(); temp = tempPossibilities[field]['flips'].split('|'); for (var i = 0; i < temp.length-1; i++) { Spielfeld[(temp[i])] = color; } }
The following function is used to make changes to a certain position on the board.
function putPiece(color, field) {
5 for the color parameter is used for the flipcounts. Those of course are only shown if it's the player's turn, i.e. if progress is 0.
if (color == 5 && progress == 0) { document.getElementById('O'+field).className = 'game point'; if (showHelp == 1) document.getElementById('O'+field).firstChild.nodeValue = possibilities[field]['anzahl']; }
0 for color is used to clear a field.
else if (color == 0 || (color == 5 && progress == 1)) { document.getElementById('O'+field).className = 'game normal'; document.getElementById('O'+field).firstChild.nodeValue = ' '; }
The other colors are for white and black. The following part either flips a disc or puts a new one. After the rotating animation is played, it's also replaced by a static image.
else { if (Spielfeld[field] != 0) { document.getElementById('O'+field).className = (color == 1) ? 'towhite' : 'toblack'; setTimeout("document.getElementById('O"+field+"').className = "+ ((color == 1) ? "'white'; " : "'black'; "), 1500); } else document.getElementById('O'+field).className = (color == 1) ? 'white' : 'black'; Spielfeld[field] = color; document.getElementById('O'+field).firstChild.nodeValue = ' ';
Now the discs for both parties are counted.
white = 0; black = 0; for (j = 0; j < 64; j++) { if (Spielfeld[j] == 1) white++; if (Spielfeld[j] == 2) black++; } document.getElementById('white').firstChild.nodeValue = white; document.getElementById('black').firstChild.nodeValue = black; } }
What follows is quite a huge function. This is for determining the legal possible moves for either player. Those are added to the possibilities[] array.
function checkPossibilities(color) { var Reihe, Spalte; var i, j, k; var flips = ''; var anzahl = 0;
At first this array has to be reset.
for (i = 0; i < 64; i++) { possibilities[i]['anzahl'] = 0; possibilities[i]['flips'] = ''; }
Afterwards the color of the opponent is determined.
var opponent = (color == 1) ? 2 : 1;
Now for each disc of color there are some checks.
for (i = 0; i < 64; i++) { if (Spielfeld[i] == color) {
The row and column of the current disc are calculated.
Reihe = Math.floor(i/8); Spalte = i%8;
Now the current row is checked for empty fields. Of course the current field and also the adjacent fields are left out.
for (j = 0; j < 8; j++) { if (j == Spalte || j == Spalte-1 || j == Spalte+1) continue;
If an empty field is found it's checked if all the fields between the selected field and the empty one belong to the opponent. If this is the case anzahl is incremented and the fields are added to flips, if not both variables are reset and the next empty field is checked.
if (Spielfeld[(Reihe*8+j)] == 0) { if (Spalte > j) { for (k = 1; k < Spalte-j; k++) { if (Spielfeld[i-k] == opponent) { anzahl++; flips = flips.concat((i-k) + '|'); } else { anzahl = 0; flips = ''; break; } } possibilities[(Reihe*8+j)]['anzahl'] += anzahl; possibilities[(Reihe*8+j)]['flips'] = possibilities[(Reihe*8+j)]['flips'].concat(flips); anzahl = 0; flips = ''; } else { for (k = 1; k < j-Spalte; k++) { if (Spielfeld[i+k] == opponent) { anzahl++; flips = flips.concat((i+k) + '|'); } else { anzahl = 0; flips = ''; break; } } possibilities[(Reihe*8+j)]['anzahl'] += anzahl; possibilities[(Reihe*8+j)]['flips'] = possibilities[(Reihe*8+j)]['flips'].concat(flips); anzahl = 0; flips = ''; } } }
The same procedure for the discs in the same column...
for (j = 0; j < 8; j++) { if (j == Reihe || j == Reihe-1 || j == Reihe+1) continue; if (Spielfeld[(j*8+Spalte)] == 0) { if (Reihe > j) { for (k = 1; k < Reihe-j; k++) { if (Spielfeld[(i-k*8)] == opponent) { anzahl++; flips = flips.concat((i-k*8) + '|'); } else { anzahl = 0; flips = ''; break; } } possibilities[(j*8+Spalte)]['anzahl'] += anzahl; possibilities[(j*8+Spalte)]['flips'] = possibilities[(j*8+Spalte)]['flips'].concat(flips); anzahl = 0; flips = ''; } else { for (k = 1; k < j-Reihe; k++) { if (Spielfeld[(i+k*8)] == opponent) { anzahl++; flips = flips.concat((i+k*8) + '|'); } else { anzahl = 0; flips = ''; break; } } possibilities[(j*8+Spalte)]['anzahl'] += anzahl; possibilities[(j*8+Spalte)]['flips'] = possibilities[(j*8+Spalte)]['flips'].concat(flips); anzahl = 0; flips = ''; } } }
Accordingly for diagonal lines...
for (j = 2; j < 8; j++) { if (j <= Reihe && j <= Spalte && Spielfeld[(i-j*9)] == 0) { for (k = 1; k < j; k++) { if (Spielfeld[(i-k*9)] == opponent) { anzahl++; flips = flips.concat((i-k*9) + '|'); } else { anzahl = 0; flips = ''; break; } } possibilities[(i-j*9)]['anzahl'] += anzahl; possibilities[(i-j*9)]['flips'] = possibilities[(i-j*9)]['flips'].concat(flips); anzahl = 0; flips = ''; } if (j <= Reihe && j < 8-Spalte && Spielfeld[(i-j*7)] == 0) { for (k = 1; k < j; k++) { if (Spielfeld[i-k*7] == opponent) { anzahl++; flips = flips.concat((i-k*7) + '|'); } else { anzahl = 0; flips = ''; break; } } possibilities[(i-j*7)]['anzahl'] += anzahl; possibilities[(i-j*7)]['flips'] = possibilities[(i-j*7)]['flips'].concat(flips); anzahl = 0; flips = ''; } if ( j < 8-Reihe && j <= Spalte && Spielfeld[(i+j*7)] == 0) { for (k = 1; k < j; k++) { if (Spielfeld[i+k*7] == opponent) { anzahl++; flips = flips.concat((i+k*7) + '|'); } else { anzahl = 0; flips = ''; break; } } possibilities[(i+j*7)]['anzahl'] += anzahl; possibilities[(i+j*7)]['flips'] = possibilities[(i+j*7)]['flips'].concat(flips); anzahl = 0; flips = ''; } if (j < 8-Reihe && j < 8-Spalte && Spielfeld[(i+j*9)] == 0) { for (k = 1; k < j; k++) { if (Spielfeld[i+k*9] == opponent) { anzahl++; flips = flips.concat((i+k*9) + '|'); } else { anzahl = 0; flips = ''; break; } } possibilities[(i+j*9)]['anzahl'] += anzahl; possibilities[(i+j*9)]['flips'] = possibilities[(i+j*9)]['flips'].concat(flips); anzahl = 0; flips = ''; } } } } }
Finally the heart of the game, the artificial intelligence. I tried to make it as simple but effective as possible. Of course you can somewhat trick it if you know which moves it prefers, so if you still want to enjoy the game, go ahead to the HTML part at the bottom. However if you still want to know how it works, go on.
function KI() { var i, j, k, l; var blah, blah2, nimm; var temp = new Array(); var temp2 = new Array(); var temp3 = new Array();
The best moves are always those where the opponent cannot move in the next turn (this way you can also find out possible wipe-outs). So at first it's checked, if such moves exist. To do that, the fakeFlip() function from above is used. After flipping all the necessary discs for one possible move the checkPossibilities() function is called for the player and afterwards checkMove() checks, if the player has any legal moves. If he doesn't, this move is instantly added to the computer's choice.
for (i = 0; i < 64; i++) { tempPossibilities[i]['anzahl'] = possibilities[i]['anzahl']; tempPossibilities[i]['flips'] = possibilities[i]['flips']; tempSpielfeld[i] = Spielfeld[i]; } for (i = 0; i < 64; i++) { if (tempPossibilities[i]['anzahl'] > 0) { Spielfeld[i] = computer; fakeFlip(computer, i); } else continue; checkPossibilities(player); if (checkMove() == 0) temp2.push(i); for (j = 0; j < 64; j++) Spielfeld[j] = tempSpielfeld[j]; } for (i = 0; i < 64; i++) { possibilities[i]['anzahl'] = tempPossibilities[i]['anzahl']; possibilities[i]['flips'] = tempPossibilities[i]['flips']; Spielfeld[i] = tempSpielfeld[i]; }
If no moves were found yet the next target are the edges.
if (typeof(temp2[0]) == 'undefined') { if (possibilities[0]['anzahl'] > 0) temp2.push(0); if (possibilities[7]['anzahl'] > 0) temp2.push(7); if (possibilities[56]['anzahl'] > 0) temp2.push(56); if (possibilities[63]['anzahl'] > 0) temp2.push(63);
If there's still no success, check the fields on the boarders except for those adjacent to the edges. Those are of the same importance under the circumstance, that the edge is already occupied, so this is checked directly afterwards.
if (typeof(temp2[0]) == 'undefined') { for (i = 2; i < 6; i++) { if (possibilities[i]['anzahl'] > 0) temp2.push(i); if (possibilities[(i*8)]['anzahl'] > 0) temp2.push(i*8); if (possibilities[(i*8+7)]['anzahl'] > 0) temp2.push(i*8+7); if (possibilities[(56+i)]['anzahl'] > 0) temp2.push(56+i); } if (Spielfeld[0] == computer && possibilities[1]['anzahl'] > 0) temp2.push(1); if (Spielfeld[0] == computer && possibilities[8]['anzahl'] > 0) temp2.push(8); if (Spielfeld[7] == computer && possibilities[6]['anzahl'] > 0) temp2.push(6); if (Spielfeld[7] == computer && possibilities[15]['anzahl'] > 0) temp2.push(15); if (Spielfeld[56] == computer && possibilities[48]['anzahl'] > 0) temp2.push(48); if (Spielfeld[56] == computer && possibilities[57]['anzahl'] > 0) temp2.push(57); if (Spielfeld[63] == computer && possibilities[55]['anzahl'] > 0) temp2.push(55); if (Spielfeld[63] == computer && possibilities[62]['anzahl'] > 0) temp2.push(62);
If still no move was found, the third row and column from the outside is checked and the fields diagonally adjacent to the edges, if the edge is already conquered.
if (typeof(temp2[0]) == 'undefined') { for (i = 2; i < 6; i++) { if (possibilities[(16+i)]['anzahl'] > 0) temp2.push(16+i); if (possibilities[(i*8+2)]['anzahl'] > 0) temp2.push(i*8+2); if (possibilities[(i*8+5)]['anzahl'] > 0) temp2.push(i*8+5); if (possibilities[(40+i)]['anzahl'] > 0) temp2.push(40+i); } if (Spielfeld[0] == computer && possibilities[9]['anzahl'] > 0) temp2.push(9); if (Spielfeld[7] == computer && possibilities[14]['anzahl'] > 0) temp2.push(14); if (Spielfeld[56] == computer && possibilities[49]['anzahl'] > 0) temp2.push(49); if (Spielfeld[63] == computer && possibilities[54]['anzahl'] > 0) temp2.push(54);
You know the deal... no move found yet, so look further. This time for the second row and column from the outside...
if (typeof(temp2[0]) == 'undefined') { for (i = 2; i < 6; i++) { if (possibilities[(8+i)]['anzahl'] > 0) temp2.push(8+i); if (possibilities[(i*8+1)]['anzahl'] > 0) temp2.push(i*8+1); if (possibilities[(i*8+6)]['anzahl'] > 0) temp2.push(i*8+6); if (possibilities[(48+i)]['anzahl'] > 0) temp2.push(48+i); }
... the horizontal and vertical adjacent fields to the edges, no matter if the edge is conquered or not...
if (typeof(temp2[0]) == 'undefined') { if (possibilities[1]['anzahl'] > 0) temp2.push(1); if (possibilities[6]['anzahl'] > 0) temp2.push(6); if (possibilities[8]['anzahl'] > 0) temp2.push(8); if (possibilities[15]['anzahl'] > 0) temp2.push(15); if (possibilities[48]['anzahl'] > 0) temp2.push(48); if (possibilities[55]['anzahl'] > 0) temp2.push(55); if (possibilities[57]['anzahl'] > 0) temp2.push(57); if (possibilities[62]['anzahl'] > 0) temp2.push(62);
... and finally the diagonally adjacent fields. Now all fields were checked.
if (typeof(temp2[0]) == 'undefined') { if (possibilities[9]['anzahl'] > 0) temp2.push(9); if (possibilities[14]['anzahl'] > 0) temp2.push(14); if (possibilities[49]['anzahl'] > 0) temp2.push(49); if (possibilities[54]['anzahl'] > 0) temp2.push(54); } } } } } }
Now the computer has to choose the best move from those that were found out. During the first 10 moves those are decided by chance. This way the computer may choose moves that leave him with far less discs on the board than his opponent. During the beginning of the game this can be a mobility advantage. Also the decision by chance makes the AI more flexible, because it doesn't choose the same moves for the same opening again and again.
if (white + black < 25) { for (i = 0; i < temp2.length; i++) temp3[i] = temp2[i]; }
Later it chooses the move with the highest difference between the now won values and the possible values of the best couter move the opponent can take. This is the point where the wert[] array from the beginning becomes important.
else { var Differenz = -1000; if (temp2.length > 1) { for (i = 0; i < 64; i++) { tempPossibilities[i]['anzahl'] = possibilities[i]['anzahl']; tempPossibilities[i]['flips'] = possibilities[i]['flips']; tempSpielfeld[i] = Spielfeld[i]; } for (i = 0; i < temp2.length; i++) { temp = possibilities[(temp2[i])]['flips'].split('|'); blah = 0; for (j = 0; j < temp.length-1; j++) blah += wert[(temp[j])]; Spielfeld[(temp2[i])] = computer; fakeFlip(computer, (temp2[i])); checkPossibilities(player); for (j = 0; j < 64; j++) { temp = possibilities[j]['flips'].split('|'); blah2 = 0; for (k = 0; k < temp.length-1; k++) blah2 += wert[(temp[k])]; } for (j = 0; j < 64; j++) { possibilities[j]['anzahl'] = tempPossibilities[j]['anzahl']; possibilities[j]['flips'] = tempPossibilities[j]['flips']; Spielfeld[j] = tempSpielfeld[j]; } if (blah - blah2 > Differenz) { while (typeof(temp3[0]) != 'undefined') { temp3.pop(); } temp3.push(temp2[i]); Differenz = blah - blah2; } else if (Differenz == blah - blah2) { temp3.push(temp2[i]); } } } else temp3[0] = temp2[0]; }
Still there can be more than one move of the same importance. So finally a move is chosen by chance.
blah = Math.floor((Math.random() * 1000) % temp3.length); nimm = temp3[blah]; putPiece(computer, nimm); flip(computer, nimm);
Finally the possibilities for the player are checked. If he can move the control is given to him, if not the AI calls itself again after 2 seconds. However if the AI cannot move itself the game is over.
progress = 0; checkPossibilities(player); if (checkMove() == 0) { checkPossibilities(computer); if (checkMove() == 0) gameOver(); else setTimeout("KI()", 2000); } else for (i = 0; i < 64; i++) { if (possibilities[i]['anzahl'] > 0) putPiece (5,i); else if (Spielfeld[i] == 0) putPiece(0,i); } } </script>
What's still missing is the HTML part. We need two counters for the current amount of discs on the board:
<img src="black.gif" height="30" width="30" alt="black"> <br> <span style="font-size: 20pt; font-weight: bold" id="black">&nbsp; </span> <img src="white.gif" height="30" width="30" alt="white"> <br> <span style="font-size: 20pt; font-weight: bold" id="white">&nbsp; </span>
Also the board itself has to be created.
<script type="text/javascript"> document.write('<table border="3" class="spiel">'); for (var i = 0; i < 8; i++) { document.write('<tr>'); for (var j = 0; j < 8; j++) { document.write('<td id="O'+(i*8+j)+'" onClick="if (progress == 0) put('+(i*8+j)+')">&nbsp; <\/td>'); } document.write('<\/tr>'); } document.write('<\/table>'); </script>
And finally the New Game button as well as the radio buttons to choose the color and if help should be displayed or not.
<table width="100%" id="nG"> <tr> <td width="33%"> <input type="radio" name="Color" onClick="player=2; computer=1" id="C0" checked> <label for="C0">Player Black, Computer White</label> <br> <input type="radio" name="Color" onClick="player=1; computer=2" id="C1"> <label for="C1">Player White, Computer Black</label> </td> <td width="34%" align="center"> <input type="button" value="New Game" onclick="startgame(); this.blur()"> </td> <td width="33%"> <input type="radio" name="Help" onClick="showHelp=0" id="H0" checked> <label for="H0">do not show help</label> <br> <input type="radio" name="Help" onClick="showHelp=1" id="H1"> <label for="H1">show help</label> </td> </tr> </table>
The last thing to do is to initialize the game.
<script type="text/javascript"> init(); </script>