mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-01-15 06:23:15 -08:00
Removed spaces from top-level directory names.
Spaces tend to cause annoyances in a Unix-style shell environment. This change fixes that.
This commit is contained in:
7
04_Awari/README.md
Normal file
7
04_Awari/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
### Awari
|
||||
|
||||
As published in Basic Computer Games (1978)
|
||||
https://www.atariarchives.org/basicgames/showpage.php?page=6
|
||||
|
||||
Downloaded from Vintage Basic at
|
||||
http://www.vintage-basic.net/games.html
|
||||
70
04_Awari/awari.bas
Normal file
70
04_Awari/awari.bas
Normal file
@@ -0,0 +1,70 @@
|
||||
5 PRINT TAB(34);"AWARI"
|
||||
7 PRINT TAB(15);"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"
|
||||
10 DATA 0
|
||||
15 DIM B(13),G(13),F(50):READ N
|
||||
20 PRINT:PRINT:E=0
|
||||
25 FOR I=0 TO 12:B(I)=3:NEXT I
|
||||
30 C=0:F(N)=0:B(13)=0:B(6)=0
|
||||
35 GOSUB 500
|
||||
40 PRINT "YOUR MOVE";:GOSUB 110
|
||||
45 IF E=0 THEN 80
|
||||
50 IF M=H THEN GOSUB 100
|
||||
55 IF E=0 THEN 80
|
||||
60 PRINT "MY MOVE IS ";:GOSUB 800
|
||||
65 IF E=0 THEN 80
|
||||
70 IF M=H THEN PRINT ",";:GOSUB 800
|
||||
75 IF E>0 THEN 35
|
||||
80 PRINT:PRINT"GAME OVER"
|
||||
85 D=B(6)-B(13):IF D<0 THEN PRINT "I WIN BY";-D;"POINTS":GOTO 20
|
||||
90 N=N+1:IF D=0 THEN PRINT "DRAWN GAME":GOTO 20
|
||||
95 PRINT "YOU WIN BY";D;"POINTS":GOTO 20
|
||||
100 PRINT "AGAIN";
|
||||
110 INPUT M:IF M<7 THEN IF M>0 THEN M=M-1:GOTO 130
|
||||
120 PRINT "ILLEGAL MOVE":GOTO 100
|
||||
130 IF B(M)=0 THEN 120
|
||||
140 H=6:GOSUB 200
|
||||
150 GOTO 500
|
||||
200 K=M:GOSUB 600
|
||||
205 E=0:IF K>6 THEN K=K-7
|
||||
210 C=C+1:IF C<9 THEN F(N)=F(N)*6+K
|
||||
215 FOR I=0 TO 5:IF B(I)<>0 THEN 230
|
||||
220 NEXT I
|
||||
225 RETURN
|
||||
230 FOR I=7 TO 12:IF B(I)<>0 THEN E=1:RETURN
|
||||
235 GOTO 220
|
||||
500 PRINT:PRINT" ";
|
||||
505 FOR I=12 TO 7 STEP -1:GOSUB 580
|
||||
510 NEXT I
|
||||
515 PRINT:I=13:GOSUB 580
|
||||
520 PRINT " ";:PRINT B(6):PRINT " ";
|
||||
525 FOR I=0 TO 5:GOSUB 580
|
||||
530 NEXT I
|
||||
535 PRINT:PRINT:RETURN
|
||||
580 IF B(I)<10 THEN PRINT " ";
|
||||
585 PRINT B(I);:RETURN
|
||||
600 P=B(M):B(M)=0
|
||||
605 FOR P=P TO 1 STEP -1:M=M+1:IF M>13 THEN M=M-14
|
||||
610 B(M)=B(M)+1:NEXT P
|
||||
615 IF B(M)=1 THEN IF M<>6 THEN IF M<>13 THEN IF B(12-M)<>0 THEN 625
|
||||
620 RETURN
|
||||
625 B(H)=B(H)+B(12-M)+1:B(M)=0:B(12-M)=0:RETURN
|
||||
800 D=-99:H=13
|
||||
805 FOR I=0 TO 13:G(I)=B(I):NEXT I
|
||||
810 FOR J=7 TO 12:IF B(J)=0 THEN 885
|
||||
815 Q=0:M=J:GOSUB 600
|
||||
820 FOR I=0 TO 5:IF B(I)=0 THEN 845
|
||||
825 L=B(I)+I:R=0
|
||||
830 IF L>13 THEN L=L-14:R=1:GOTO 830
|
||||
835 IF B(L)=0 THEN IF L<>6 THEN IF L<>13 THEN R=B(12-L)+R
|
||||
840 IF R>Q THEN Q=R
|
||||
845 NEXT I
|
||||
850 Q=B(13)-B(6)-Q:IF C>8 THEN 875
|
||||
855 K=J:IF K>6 THEN K=K-7
|
||||
860 FOR I=0 TO N-1:IF F(N)*6+K=INT(F(I)/6^(7-C)+.1) THEN Q=Q-2
|
||||
870 NEXT I
|
||||
875 FOR I=0 TO 13:B(I)=G(I):NEXT I
|
||||
880 IF Q>=D THEN A=J:D=Q
|
||||
885 NEXT J
|
||||
890 M=A:PRINT CHR$(42+M);:GOTO 200
|
||||
900 FOR I=0 TO N-1:PRINT B(I):NEXT I
|
||||
999 END
|
||||
3
04_Awari/csharp/README.md
Normal file
3
04_Awari/csharp/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||
|
||||
Conversion to [Microsoft C#](https://docs.microsoft.com/en-us/dotnet/csharp/)
|
||||
206
04_Awari/java/Awari.java
Normal file
206
04_Awari/java/Awari.java
Normal file
@@ -0,0 +1,206 @@
|
||||
import java.util.Scanner;
|
||||
import java.util.Random;
|
||||
|
||||
public class Awari{
|
||||
int []board;
|
||||
private final int playerPits;
|
||||
private final int computerPits;
|
||||
private final int playerHome;
|
||||
private final int computerHome;
|
||||
Scanner input;
|
||||
int sumPlayer;
|
||||
int sumComputer;
|
||||
Awari(){
|
||||
input = new Scanner(System.in);
|
||||
playerPits = 0;
|
||||
computerPits = 7;
|
||||
playerHome = 6;
|
||||
computerHome = 13;
|
||||
sumPlayer = 18;
|
||||
sumComputer = 18;
|
||||
board = new int [14];
|
||||
for (int i=0;i<6;i++){
|
||||
board[playerPits+i]=3;
|
||||
board[computerPits+i]=3;
|
||||
}
|
||||
System.out.println(" AWARI");
|
||||
System.out.println("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
|
||||
printBoard();
|
||||
playerMove(true);
|
||||
}
|
||||
|
||||
private void printBoard(){
|
||||
System.out.print("\n ");
|
||||
for (int i=0;i<6;i++){
|
||||
System.out.print(String.format("%2d",board[12-i]));
|
||||
System.out.print(" ");
|
||||
}
|
||||
System.out.println("");
|
||||
System.out.print(String.format("%2d",board[computerHome]));
|
||||
System.out.print(" ");
|
||||
System.out.println(String.format("%2d",board[playerHome]));
|
||||
System.out.print(" ");
|
||||
for(int i=0;i<6;i++){
|
||||
System.out.print(String.format("%2d",board[playerPits+i]));
|
||||
System.out.print(" ");
|
||||
}
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
private void playerMove(boolean val){
|
||||
System.out.println("\nComputerSum PlayerSum"+sumComputer+" "+sumPlayer);
|
||||
if(val == true)
|
||||
System.out.print("YOUR MOVE? ");
|
||||
else
|
||||
System.out.print("AGAIN? ");
|
||||
int move = input.nextInt();
|
||||
while(move<1||move>6||board[move-1]==0){
|
||||
System.out.print("INVALID MOVE!!! TRY AGAIN ");
|
||||
move = input.nextInt();
|
||||
}
|
||||
int seeds = board[move-1];
|
||||
board[move-1] = 0;
|
||||
sumPlayer -= seeds;
|
||||
int last_pos = distribute(seeds,move);
|
||||
if(last_pos == playerHome){
|
||||
printBoard();
|
||||
if(isGameOver(true)){
|
||||
System.exit(0);
|
||||
}
|
||||
playerMove(false);
|
||||
}
|
||||
else if(board[last_pos] == 1&&last_pos != computerHome){
|
||||
int opp = calculateOpposite(last_pos);
|
||||
if(last_pos<6){
|
||||
sumPlayer+=board[opp];
|
||||
sumComputer-=board[opp];
|
||||
}
|
||||
else{
|
||||
sumComputer+=board[opp];
|
||||
sumPlayer-=board[opp];
|
||||
}
|
||||
board[last_pos]+=board[opp];
|
||||
board[opp] = 0;
|
||||
printBoard();
|
||||
if(isGameOver(false)){
|
||||
System.exit(0);
|
||||
}
|
||||
computerMove(true);
|
||||
}
|
||||
else{
|
||||
printBoard();
|
||||
if(isGameOver(false)){
|
||||
System.exit(0);
|
||||
}
|
||||
computerMove(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void computerMove(boolean value){
|
||||
int val=-1;
|
||||
System.out.println("\nComputerSum PlayerSum"+sumComputer+" "+sumPlayer);
|
||||
for(int i=0;i<6;i++){
|
||||
if(6-i == board[computerPits+i])
|
||||
val = i;
|
||||
}
|
||||
int move ;
|
||||
if(val == -1)
|
||||
{
|
||||
Random random = new Random();
|
||||
move = random.nextInt(6)+computerPits;
|
||||
while(board[move] == 0){
|
||||
move = random.nextInt(6)+computerPits;
|
||||
}
|
||||
if(value == true)
|
||||
System.out.println(String.format("MY MOVE IS %d ",move-computerPits+1));
|
||||
else
|
||||
System.out.println(String.format(",%d",move-computerPits+1));
|
||||
int seeds = board[move];
|
||||
board[move] = 0;
|
||||
sumComputer-=seeds;
|
||||
int last_pos = distribute(seeds,move+1);
|
||||
if(board[last_pos] == 1&&last_pos != playerHome){
|
||||
int opp = calculateOpposite(last_pos);
|
||||
if(last_pos<6){
|
||||
sumPlayer+=board[opp];
|
||||
sumComputer-=board[opp];
|
||||
}
|
||||
else{
|
||||
sumComputer+=board[opp];
|
||||
sumPlayer-=board[opp];
|
||||
}
|
||||
board[last_pos]+=board[opp];
|
||||
board[opp] = 0;
|
||||
printBoard();
|
||||
if(isGameOver(false)){
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
else{
|
||||
printBoard();
|
||||
if(isGameOver(false)){
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
playerMove(true);
|
||||
}
|
||||
else {
|
||||
move = val+computerPits;
|
||||
if(value == true)
|
||||
System.out.print(String.format("MY MOVE IS %d",move-computerPits+1));
|
||||
else
|
||||
System.out.print(String.format(",%d",move-computerPits+1));
|
||||
int seeds = board[move];
|
||||
board[move] = 0;
|
||||
sumComputer-=seeds;
|
||||
int last_pos = distribute(seeds,move+1);
|
||||
if(last_pos == computerHome){
|
||||
if(isGameOver(true) ){
|
||||
System.exit(0);
|
||||
}
|
||||
computerMove(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int distribute(int seeds, int pos){
|
||||
while(seeds!=0){
|
||||
if(pos==14)
|
||||
pos=0;
|
||||
if(pos<6)
|
||||
sumPlayer++;
|
||||
else if(pos>6&&pos<13)
|
||||
sumComputer++;
|
||||
board[pos]++;
|
||||
pos++;
|
||||
seeds--;
|
||||
}
|
||||
return pos-1;
|
||||
}
|
||||
|
||||
private int calculateOpposite(int pos){
|
||||
return 12-pos;
|
||||
}
|
||||
|
||||
private boolean isGameOver(boolean show){
|
||||
if(sumPlayer == 0 || sumComputer == 0){
|
||||
if(show)
|
||||
printBoard();
|
||||
System.out.println("GAME OVER");
|
||||
if(board[playerHome]>board[computerHome]){
|
||||
System.out.println(String.format("YOU WIN BY %d POINTS",board[playerHome]-board[computerHome]));
|
||||
}
|
||||
else if(board[playerHome]<board[computerHome]){
|
||||
System.out.println(String.format("YOU LOSE BY %d POINTS",board[computerHome]-board[playerHome]));
|
||||
}
|
||||
else{
|
||||
System.out.println("DRAW");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
5
04_Awari/java/AwariGame.java
Normal file
5
04_Awari/java/AwariGame.java
Normal file
@@ -0,0 +1,5 @@
|
||||
public class AwariGame {
|
||||
public static void main(String[] args) {
|
||||
Awari awari = new Awari();
|
||||
}
|
||||
}
|
||||
3
04_Awari/java/README.md
Normal file
3
04_Awari/java/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||
|
||||
Conversion to [Oracle Java](https://openjdk.java.net/)
|
||||
3
04_Awari/javascript/README.md
Normal file
3
04_Awari/javascript/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||
|
||||
Conversion to [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Shells)
|
||||
9
04_Awari/javascript/awari.html
Normal file
9
04_Awari/javascript/awari.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>AWARI</title>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="output" style="font-size: 12pt;"></pre>
|
||||
<script src="awari.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
259
04_Awari/javascript/awari.js
Normal file
259
04_Awari/javascript/awari.js
Normal file
@@ -0,0 +1,259 @@
|
||||
// AWARI
|
||||
//
|
||||
// Converted from BASIC to Javascript by Oscar Toledo G. (nanochess)
|
||||
//
|
||||
|
||||
function print(str)
|
||||
{
|
||||
document.getElementById("output").appendChild(document.createTextNode(str));
|
||||
}
|
||||
|
||||
function input()
|
||||
{
|
||||
var input_element;
|
||||
var input_str;
|
||||
|
||||
return new Promise(function (resolve) {
|
||||
input_element = document.createElement("INPUT");
|
||||
|
||||
print("? ");
|
||||
input_element.setAttribute("type", "text");
|
||||
input_element.setAttribute("length", "50");
|
||||
document.getElementById("output").appendChild(input_element);
|
||||
input_element.focus();
|
||||
input_str = undefined;
|
||||
input_element.addEventListener("keydown", function (event) {
|
||||
if (event.keyCode == 13) {
|
||||
input_str = input_element.value;
|
||||
document.getElementById("output").removeChild(input_element);
|
||||
print(input_str);
|
||||
print("\n");
|
||||
resolve(input_str);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function tab(space)
|
||||
{
|
||||
var str = "";
|
||||
while (space-- > 0)
|
||||
str += " ";
|
||||
return str;
|
||||
}
|
||||
|
||||
print(tab(34) + "AWARI\n");
|
||||
print(tab(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n");
|
||||
|
||||
n = 0;
|
||||
|
||||
b = [0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
||||
g = [0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
||||
f = [];
|
||||
for (i = 0; i <= 50; i++) {
|
||||
f[i] = 0;
|
||||
}
|
||||
|
||||
function show_number(number)
|
||||
{
|
||||
if (number < 10)
|
||||
print(" " + number + " ");
|
||||
else
|
||||
print(" " + number + " ");
|
||||
}
|
||||
|
||||
function show_board()
|
||||
{
|
||||
var i;
|
||||
|
||||
print("\n");
|
||||
print(" ");
|
||||
for (i = 12; i >= 7; i--)
|
||||
show_number(b[i]);
|
||||
print("\n");
|
||||
i = 13;
|
||||
show_number(b[i]);
|
||||
print(" " + b[6] + "\n");
|
||||
print(" ");
|
||||
for (i = 0; i <= 5; i++)
|
||||
show_number(b[i]);
|
||||
print("\n");
|
||||
print("\n");
|
||||
}
|
||||
|
||||
function do_move()
|
||||
{
|
||||
k = m;
|
||||
adjust_board();
|
||||
e = 0;
|
||||
if (k > 6)
|
||||
k -= 7;
|
||||
c++;
|
||||
if (c < 9)
|
||||
f[n] = f[n] * 6 + k
|
||||
for (i = 0; i <= 5; i++) {
|
||||
if (b[i] != 0) {
|
||||
for (i = 7; i <= 12; i++) {
|
||||
if (b[i] != 0) {
|
||||
e = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function adjust_board()
|
||||
{
|
||||
p = b[m];
|
||||
b[m] = 0;
|
||||
while (p >= 1) {
|
||||
m++;
|
||||
if (m > 13)
|
||||
m -= 14;
|
||||
b[m]++;
|
||||
p--;
|
||||
}
|
||||
if (b[m] == 1) {
|
||||
if (m != 6 && m != 13) {
|
||||
if (b[12 - m] != 0) {
|
||||
b[h] += b[12 - m] + 1;
|
||||
b[m] = 0;
|
||||
b[12 - m] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function computer_move()
|
||||
{
|
||||
d = -99;
|
||||
h = 13;
|
||||
for (i = 0; i<= 13; i++) // Backup board
|
||||
g[i] = b[i];
|
||||
for (j = 7; j <= 12; j++) {
|
||||
if (b[j] == 0)
|
||||
continue;
|
||||
q = 0;
|
||||
m = j;
|
||||
adjust_board();
|
||||
for (i = 0; i <= 5; i++) {
|
||||
if (b[i] == 0)
|
||||
continue;
|
||||
l = b[i] + i;
|
||||
r = 0;
|
||||
while (l > 13) {
|
||||
l -= 14;
|
||||
r = 1;
|
||||
}
|
||||
if (b[l] == 0) {
|
||||
if (l != 6 && l != 13)
|
||||
r = b[12 - l] + r;
|
||||
}
|
||||
if (r > q)
|
||||
q = r;
|
||||
}
|
||||
q = b[13] - b[6] - q;
|
||||
if (c < 8) {
|
||||
k = j;
|
||||
if (k > 6)
|
||||
k -= 7;
|
||||
for (i = 0; i <= n - 1; i++) {
|
||||
if (f[n] * 6 + k == Math.floor(f[i] / Math.pow(7 - c, 6) + 0.1))
|
||||
q -= 2;
|
||||
}
|
||||
}
|
||||
for (i = 0; i <= 13; i++) // Restore board
|
||||
b[i] = g[i];
|
||||
if (q >= d) {
|
||||
a = j;
|
||||
d = q;
|
||||
}
|
||||
}
|
||||
m = a;
|
||||
print(m - 6);
|
||||
do_move();
|
||||
}
|
||||
|
||||
// Main program
|
||||
async function main()
|
||||
{
|
||||
while (1) {
|
||||
print("\n");
|
||||
print("\n");
|
||||
e = 0;
|
||||
for (i = 0; i <= 12; i++)
|
||||
b[i] = 3;
|
||||
|
||||
c = 0;
|
||||
f[n] = 0;
|
||||
b[13] = 0;
|
||||
b[6] = 0;
|
||||
|
||||
while (1) {
|
||||
show_board();
|
||||
print("YOUR MOVE");
|
||||
while (1) {
|
||||
m = parseInt(await input());
|
||||
if (m < 7) {
|
||||
if (m > 0) {
|
||||
m--;
|
||||
if (b[m] != 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
print("ILLEGAL MOVE\n");
|
||||
print("AGAIN");
|
||||
}
|
||||
h = 6;
|
||||
do_move();
|
||||
show_board();
|
||||
if (e == 0)
|
||||
break;
|
||||
if (m == h) {
|
||||
print("AGAIN");
|
||||
while (1) {
|
||||
m = parseInt(await input());
|
||||
if (m < 7) {
|
||||
if (m > 0) {
|
||||
m--;
|
||||
if (b[m] != 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
print("ILLEGAL MOVE\n");
|
||||
print("AGAIN");
|
||||
}
|
||||
h = 6;
|
||||
do_move();
|
||||
show_board();
|
||||
}
|
||||
if (e == 0)
|
||||
break;
|
||||
print("MY MOVE IS ");
|
||||
computer_move();
|
||||
if (e == 0)
|
||||
break;
|
||||
if (m == h) {
|
||||
print(",");
|
||||
computer_move();
|
||||
}
|
||||
if (e == 0)
|
||||
break;
|
||||
}
|
||||
print("\n");
|
||||
print("GAME OVER\n");
|
||||
d = b[6] - b[13];
|
||||
if (d < 0)
|
||||
print("I WIN BY " + -d + " POINTS\n");
|
||||
else if (d == 0) {
|
||||
n++;
|
||||
print("DRAWN GAME\n");
|
||||
} else {
|
||||
n++;
|
||||
print("YOU WIN BY " + d + " POINTS\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
3
04_Awari/pascal/README.md
Normal file
3
04_Awari/pascal/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||
|
||||
Conversion to [Pascal](https://en.wikipedia.org/wiki/Pascal_(programming_language))
|
||||
3
04_Awari/perl/README.md
Normal file
3
04_Awari/perl/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||
|
||||
Conversion to [Perl](https://www.perl.org/)
|
||||
3
04_Awari/python/README.md
Normal file
3
04_Awari/python/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||
|
||||
Conversion to [Python](https://www.python.org/about/)
|
||||
402
04_Awari/python/awari.py
Normal file
402
04_Awari/python/awari.py
Normal file
@@ -0,0 +1,402 @@
|
||||
"""
|
||||
AWARI
|
||||
|
||||
An ancient African game (see also Kalah, Mancala).
|
||||
|
||||
Ported by Dave LeCompte
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
PORTING NOTES
|
||||
|
||||
This game started out as 70 lines of BASIC, and I have ported it
|
||||
before. I find it somewhat amazing how efficient (densely packed) the
|
||||
original code is. Of course, the original code has fairly cryptic
|
||||
variable names (as was forced by BASIC's limitation on long (2+
|
||||
character) variable names). I have done my best here to interpret what
|
||||
each variable is doing in context, and rename them appropriately.
|
||||
|
||||
I have endeavored to leave the logic of the code in place, as it's
|
||||
interesting to see a 2-ply game tree evaluation written in BASIC,
|
||||
along with what a reader in 2021 would call "machine learning".
|
||||
|
||||
As each game is played, the move history is stored as base-6
|
||||
digits stored losing_book[game_number]. If the human player wins or
|
||||
draws, the computer increments game_number, effectively "recording"
|
||||
that loss to be referred to later. As the computer evaluates moves, it
|
||||
checks the potential game state against these losing game records, and
|
||||
if the potential move matches with the losing game (up to the current
|
||||
number of moves), that move is evaluated at a two point penalty.
|
||||
|
||||
Compare this, for example with MENACE, a mechanical device for
|
||||
"learning" tic-tac-toe:
|
||||
https://en.wikipedia.org/wiki/Matchbox_Educable_Noughts_and_Crosses_Engine
|
||||
|
||||
The base-6 representation allows game history to be VERY efficiently
|
||||
represented. I considered whether to rewrite this representation to be
|
||||
easier to read, but I elected to TRY to document it, instead.
|
||||
|
||||
Another place where I have made a difficult decision between accuracy
|
||||
and correctness is inside the "wrapping" code where it considers
|
||||
"while human_move_end > 13". The original BASIC code reads:
|
||||
|
||||
830 IF L>13 THEN L=L-14:R=1:GOTO 830
|
||||
|
||||
I suspect that the intention is not to assign 1 to R, but to increment
|
||||
R. I discuss this more in a porting note comment next to the
|
||||
translated code. If you wish to play a more accurate version of the
|
||||
game as written in the book, you can convert the increment back to an
|
||||
assignment.
|
||||
|
||||
|
||||
I continue to be impressed with this jewel of a game; as soon as I had
|
||||
the AI playing against me, it was beating me. I've been able to score
|
||||
a few wins against the computer, but even at its 2-ply lookahead, it
|
||||
beats me nearly always. I would like to become better at this game to
|
||||
explore the effectiveness of the "losing book" machine learning.
|
||||
|
||||
|
||||
EXERCISES FOR THE READER
|
||||
One could go many directions with this game:
|
||||
|
||||
- change the initial number of stones in each pit
|
||||
|
||||
- change the number of pits
|
||||
|
||||
- only allow capturing if you end on your side of the board
|
||||
|
||||
- don't allow capturing at all
|
||||
|
||||
- don't drop a stone into the enemy "home"
|
||||
|
||||
- go clockwise, instead
|
||||
|
||||
- allow the player to choose to go clockwise or counterclockwise
|
||||
|
||||
- instead of a maximum of two moves, allow each move that ends on the
|
||||
"home" to be followed by a free move.
|
||||
|
||||
- increase the AI lookahead
|
||||
|
||||
- make the scoring heuristic a little more nuanced
|
||||
|
||||
- store history to a file on disk (or in the cloud!) to allow the AI
|
||||
to learn over more than a single session
|
||||
|
||||
"""
|
||||
|
||||
|
||||
game_number = 0
|
||||
move_count = 0
|
||||
losing_book = []
|
||||
n = 0
|
||||
|
||||
MAX_HISTORY = 9
|
||||
LOSING_BOOK_SIZE = 50
|
||||
|
||||
|
||||
def print_with_tab(space_count, msg):
|
||||
if space_count > 0:
|
||||
spaces = " " * space_count
|
||||
else:
|
||||
spaces = ""
|
||||
print(spaces + msg)
|
||||
|
||||
|
||||
def draw_pit(line, board, pit_index):
|
||||
val = board[pit_index]
|
||||
line = line + " "
|
||||
if val < 10:
|
||||
line = line + " "
|
||||
line = line + str(val) + " "
|
||||
return line
|
||||
|
||||
|
||||
def draw_board(board):
|
||||
print()
|
||||
|
||||
# Draw the top (computer) pits
|
||||
line = " "
|
||||
for i in range(12, 6, -1):
|
||||
line = draw_pit(line, board, i)
|
||||
print(line)
|
||||
|
||||
# Draw the side (home) pits
|
||||
line = draw_pit("", board, 13)
|
||||
line += " " * 24
|
||||
line = draw_pit(line, board, 6)
|
||||
print(line)
|
||||
|
||||
# Draw the bottom (player) pits
|
||||
line = " "
|
||||
for i in range(0, 6):
|
||||
line = draw_pit(line, board, i)
|
||||
print(line)
|
||||
print()
|
||||
print()
|
||||
|
||||
|
||||
def play_game(board):
|
||||
# Place the beginning stones
|
||||
for i in range(0, 13):
|
||||
board[i] = 3
|
||||
|
||||
# Empty the home pits
|
||||
board[6] = 0
|
||||
board[13] = 0
|
||||
|
||||
global move_count
|
||||
move_count = 0
|
||||
|
||||
# clear the history record for this game
|
||||
losing_book[game_number] = 0
|
||||
|
||||
while True:
|
||||
draw_board(board)
|
||||
|
||||
print("YOUR MOVE")
|
||||
landing_spot, is_still_going, home = player_move(board)
|
||||
if not is_still_going:
|
||||
break
|
||||
if landing_spot == home:
|
||||
landing_spot, is_still_going, home = player_move_again(board)
|
||||
if not is_still_going:
|
||||
break
|
||||
|
||||
print("MY MOVE")
|
||||
landing_spot, is_still_going, home, msg = computer_move("", board)
|
||||
|
||||
if not is_still_going:
|
||||
print(msg)
|
||||
break
|
||||
if landing_spot == home:
|
||||
landing_spot, is_still_going, home, msg = computer_move(msg + " , ", board)
|
||||
if not is_still_going:
|
||||
print(msg)
|
||||
break
|
||||
print(msg)
|
||||
|
||||
game_over(board)
|
||||
|
||||
|
||||
def computer_move(msg, board):
|
||||
# This function does a two-ply lookahead evaluation; one computer
|
||||
# move plus one human move.
|
||||
#
|
||||
# To do this, it makes a copy (temp_board) of the board, plays
|
||||
# each possible computer move and then uses math to work out what
|
||||
# the scoring heuristic is for each possible human move.
|
||||
#
|
||||
# Additionally, if it detects that a potential move puts it on a
|
||||
# series of moves that it has recorded in its "losing book", it
|
||||
# penalizes that move by two stones.
|
||||
|
||||
best_quality = -99
|
||||
|
||||
# Make a copy of the board, so that we can experiment. We'll put
|
||||
# everything back, later.
|
||||
temp_board = board[:]
|
||||
|
||||
# For each legal computer move 7-12
|
||||
for computer_move in range(7, 13):
|
||||
if board[computer_move] == 0:
|
||||
continue
|
||||
do_move(computer_move, 13, board) # try the move (1 move lookahead)
|
||||
|
||||
best_player_move_quality = 0
|
||||
# for all legal human moves 0-5 (responses to computer move computer_move)
|
||||
for human_move_start in range(0, 6):
|
||||
if board[human_move_start] == 0:
|
||||
continue
|
||||
|
||||
human_move_end = board[human_move_start] + human_move_start
|
||||
this_player_move_quality = 0
|
||||
|
||||
# If this move goes around the board, wrap backwards.
|
||||
#
|
||||
# PORTING NOTE: The careful reader will note that I am
|
||||
# incrementing this_player_move_quality for each wrap,
|
||||
# while the original code only set it equal to 1.
|
||||
#
|
||||
# I expect this was a typo or oversight, but I also
|
||||
# recognize that you'd have to go around the board more
|
||||
# than once for this to be a difference, and even so, it
|
||||
# would be a very small difference; there are only 36
|
||||
# stones in the game, and going around the board twice
|
||||
# requires 24 stones.
|
||||
|
||||
while human_move_end > 13:
|
||||
human_move_end = human_move_end - 14
|
||||
this_player_move_quality += 1
|
||||
|
||||
if (
|
||||
(board[human_move_end] == 0)
|
||||
and (human_move_end != 6)
|
||||
and (human_move_end != 13)
|
||||
):
|
||||
# score the capture
|
||||
this_player_move_quality += board[12 - human_move_end]
|
||||
|
||||
if this_player_move_quality > best_player_move_quality:
|
||||
best_player_move_quality = this_player_move_quality
|
||||
|
||||
# This is a zero sum game, so the better the human player's
|
||||
# move is, the worse it is for the computer player.
|
||||
computer_move_quality = board[13] - board[6] - best_player_move_quality
|
||||
|
||||
if move_count < MAX_HISTORY:
|
||||
move_digit = computer_move
|
||||
if move_digit > 6:
|
||||
move_digit = move_digit - 7
|
||||
|
||||
# Calculate the base-6 history representation of the game
|
||||
# with this move. If that history is in our "losing book",
|
||||
# penalize that move.
|
||||
for prev_game_number in range(game_number):
|
||||
if losing_book[game_number] * 6 + move_digit == int(
|
||||
losing_book[prev_game_number] / 6 ^ (7 - move_count) + 0.1
|
||||
):
|
||||
computer_move_quality -= 2
|
||||
|
||||
# Copy back from temporary board
|
||||
for i in range(14):
|
||||
board[i] = temp_board[i]
|
||||
|
||||
if computer_move_quality >= best_quality:
|
||||
best_move = computer_move
|
||||
best_quality = computer_move_quality
|
||||
|
||||
selected_move = best_move
|
||||
|
||||
move_str = chr(42 + selected_move)
|
||||
if msg:
|
||||
msg += ", " + move_str
|
||||
else:
|
||||
msg = move_str
|
||||
|
||||
move_number, is_still_going, home = execute_move(selected_move, 13, board)
|
||||
|
||||
return move_number, is_still_going, home, msg
|
||||
|
||||
|
||||
def game_over(board):
|
||||
print()
|
||||
print("GAME OVER")
|
||||
|
||||
pit_difference = board[6] - board[13]
|
||||
if pit_difference < 0:
|
||||
print(f"I WIN BY {-pit_difference} POINTS")
|
||||
|
||||
else:
|
||||
global n
|
||||
n = n + 1
|
||||
|
||||
if pit_difference == 0:
|
||||
print("DRAWN GAME")
|
||||
else:
|
||||
print(f"YOU WIN BY {pit_difference} POINTS")
|
||||
|
||||
|
||||
def do_capture(m, home, board):
|
||||
board[home] += board[12 - m] + 1
|
||||
board[m] = 0
|
||||
board[12 - m] = 0
|
||||
|
||||
|
||||
def do_move(m, home, board):
|
||||
move_stones = board[m]
|
||||
board[m] = 0
|
||||
|
||||
for stones in range(move_stones, 0, -1):
|
||||
m = m + 1
|
||||
if m > 13:
|
||||
m = m - 14
|
||||
board[m] += 1
|
||||
if board[m] == 1:
|
||||
# capture
|
||||
if (m != 6) and (m != 13) and (board[12 - m] != 0):
|
||||
do_capture(m, home, board)
|
||||
return m
|
||||
|
||||
|
||||
def player_has_stones(board):
|
||||
for i in range(6):
|
||||
if board[i] > 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def computer_has_stones(board):
|
||||
for i in range(7, 13):
|
||||
if board[i] > 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def execute_move(move, home, board):
|
||||
move_digit = move
|
||||
last_location = do_move(move, home, board)
|
||||
|
||||
if move_digit > 6:
|
||||
move_digit = move_digit - 7
|
||||
|
||||
global move_count
|
||||
move_count += 1
|
||||
if move_count < MAX_HISTORY:
|
||||
# The computer keeps a chain of moves in losing_book by
|
||||
# storing a sequence of moves as digits in a base-6 number.
|
||||
#
|
||||
# game_number represents the current game,
|
||||
# losing_book[game_number] records the history of the ongoing
|
||||
# game. When the computer evaluates moves, it tries to avoid
|
||||
# moves that will lead it into paths that have led to previous
|
||||
# losses.
|
||||
losing_book[game_number] = losing_book[game_number] * 6 + move_digit
|
||||
|
||||
if player_has_stones(board) and computer_has_stones(board):
|
||||
is_still_going = True
|
||||
else:
|
||||
is_still_going = False
|
||||
return last_location, is_still_going, home
|
||||
|
||||
|
||||
def player_move_again(board):
|
||||
print("AGAIN")
|
||||
return player_move(board)
|
||||
|
||||
|
||||
def player_move(board):
|
||||
while True:
|
||||
print("SELECT MOVE 1-6")
|
||||
m = int(input()) - 1
|
||||
|
||||
if m > 5 or m < 0 or board[m] == 0:
|
||||
print("ILLEGAL MOVE")
|
||||
continue
|
||||
|
||||
break
|
||||
|
||||
ending_spot, is_still_going, home = execute_move(m, 6, board)
|
||||
|
||||
draw_board(board)
|
||||
|
||||
return ending_spot, is_still_going, home
|
||||
|
||||
|
||||
def main():
|
||||
print_with_tab(34, "AWARI")
|
||||
print_with_tab(15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY")
|
||||
print()
|
||||
print()
|
||||
|
||||
board = [0] * 14 # clear the board representation
|
||||
global losing_book
|
||||
losing_book = [0] * LOSING_BOOK_SIZE # clear the "machine learning" state
|
||||
|
||||
while True:
|
||||
play_game(board)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
3
04_Awari/ruby/README.md
Normal file
3
04_Awari/ruby/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||
|
||||
Conversion to [Ruby](https://www.ruby-lang.org/en/)
|
||||
3
04_Awari/vbnet/README.md
Normal file
3
04_Awari/vbnet/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Original BASIC source [downloaded from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||
|
||||
Conversion to [Visual Basic .NET](https://en.wikipedia.org/wiki/Visual_Basic_.NET)
|
||||
Reference in New Issue
Block a user