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:
Chris Reuter
2021-11-21 18:30:21 -05:00
parent df2e7426eb
commit d26dbf036a
1725 changed files with 0 additions and 0 deletions

18
23_Checkers/README.md Normal file
View File

@@ -0,0 +1,18 @@
### Checkers
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=40
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
The file `checkers.annotated.bas` contains an indented and annotated
version of the source code. This is no longer valid BASIC code but
should be more readable.
## Known Issues In the Original BASIC Code
- If the computer moves a checker to the bottom row, it promotes, but
leaves the original checker in place. (See line 1240)
- Human players may move non-kings as if they were kings. (See lines 1590 to 1810)
- Human players are not required to jump if it is possible.
- Curious writing to "I" variable without ever reading it. (See lines 1700 and 1806)

View File

@@ -0,0 +1,315 @@
# Annotated version of CHECKERS.BAS, modified to improve readability.
#
# I've made the following changes:
#
# 1. Added many comments and blank lines.
# 2. Separated each statement into its own line.
# 3. Indented loops, conditionals and subroutines.
# 4. Turned *SOME* conditionals and loops into
# structured-BASIC-style if/endif and loop/endloop blocks.
# 5. Switched to using '#' to delimit comments.
# 6. Subroutines now begin with "Sub_Start"
# 7. All non-string text has been converted to lower-case
# 8. All line numbers that are not jump destinations have been removed.
#
# This has helped me make sense of the code. I hope it will also help you.
#
# Print the banner
print tab(32);"CHECKERS"
print tab(15);"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"
print
print
print
print "THIS IS THE GAME OF CHECKERS. THE COMPUTER IS X,"
print "AND YOU ARE O. THE COMPUTER WILL MOVE FIRST."
print "SQUARES ARE REFERRED TO BY A COORDINATE SYSTEM."
print "(0,0) IS THE LOWER LEFT CORNER"
print "(0,7) IS THE UPPER LEFT CORNER"
print "(7,0) IS THE LOWER RIGHT CORNER"
print "(7,7) IS THE UPPER RIGHT CORNER"
print "THE COMPUTER WILL TYPE '+TO' WHEN YOU HAVE ANOTHER"
print "JUMP. TYPE TWO NEGATIVE NUMBERS IF YOU CANNOT JUMP."
print
print
print
# Declare the "globals":
# The current move: (rating, current x, current y, new x, new y)
# 'rating' represents how good the move is; higher is better.
dim r(4)
r(0)=-99 # Start with minimum score
# The board. Pieces are represented by numeric values:
#
# - 0 = empty square
# - -1,-2 = X (-1 for regular piece, -2 for king)
# - 1,2 = O (1 for regular piece, 2 for king)
#
# This program's player ("me") plays X.
dim s(7,7)
g=-1 # constant holding -1
# Initialize the board. Data is 2 length-wise strips repeated.
data 1,0,1,0,0,0,-1,0,0,1,0,0,0,-1,0,-1,15
for x=0 to 7
for y=0 to 7
read j
if j=15 then 180
s(x,y)=j
goto 200
180 restore
read s(x,y)
200 next y,x
230 # Start of game loop. First, my turn.
# For each square on the board, search for one of my pieces
# and if it can make the best move so far, store that move in 'r'
for x=0 to 7
for y=0 to 7
# Skip if this is empty or an opponent's piece
if s(x,y) > -1 then 350
# If this is one of my ordinary pieces, analyze possible
# forward moves.
if s(x,y) = -1 then
for a=-1 to 1 step 2
b=g
gosub 650
next a
endif
# If this is one of my kings, analyze possible forward
# and backward moves.
if s(x,y) = -2 then
for a=-1 to 1 step 2
for b=-1 to 1 step 2
gosub 650
next b,a
endif
350 next y,x
goto 1140 # Skip the subs
# Analyze a move from (x,y) to (x+a, y+b) and schedule it if it's
# the best candidate so far.
650 Sub_Start
u=x+a
v=y+b
# Done if it's off the board
if u<0 or u>7 or v<0 or v>7 then 870
# Consider the destination if it's empty
if s(u,v) = 0 then
gosub 910
goto 870
endif
# If it's got an opponent's piece, jump it instead
if s(u,v) > 0
# Restore u and v, then return if it's off the board
u=u+a
v=v+b
if u<0 or v<0 or u>7 or v>7 then 870
# Otherwise, consider u,v
if s(u,v)=0 then gosub 910
endif
870 return
# Evaluate jumping (x,y) to (u,v).
#
# Computes a score for the proposed move and if it's higher
# than the best-so-far move, uses that instead by storing it
# and its score in array 'r'.
910 Sub_Start
# q is the score; it starts at 0
# +2 if it promotes this piece
if v=0 and s(x,y)=-1 then q=q+2
# +5 if it takes an opponent's piece
if abs(y-v)=2 then q=q+5
# -2 if the piece is moving away from the top boundary
if y=7 then q=q-2
# +1 for putting the piece against a vertical boundary
if u=0 or u=7 then q=q+1
for c=-1 to 1 step 2
if u+c < 0 or u+c > 7 or v+g < 0 then 1080
# +1 for each adjacent friendly piece
if s(u+c, v+g) < 0 then
q=q+1
goto 1080
endif
# Prevent out-of-bounds testing
if u-c < 0 or u-c > 7 or v-g > 7 then 1080
# -2 for each opponent piece that can now take this piece here
if s(u+c,v+g) > 0 and(s(u-c,v-g)=0 or(u-c=x and v-g=y))then q=q-2
1080 next c
# Use this move if it's better than the previous best
if q>r(0) then
r(0)=q
r(1)=x
r(2)=y
r(3)=u
r(4)=v
endif
q=0 # reset the score
return
1140 if r(0)=-99 then 1880 # Game is lost if no move could be found.
# Print the computer's move. (Note: chr$(30) is an ASCII RS
# (record separator) code; probably no longer relevant.)
print chr$(30)"FROM"r(1);r(2)"TO"r(3);r(4);
r(0)=-99
# Make the computer's move. If the piece finds its way to the
# end of the board, crown it.
1240 if r(4)=0 then
s(r(3),r(4))=-2
goto 1420
endif
s(r(3),r(4))=s(r(1),r(2))
s(r(1),r(2))=0
# If the piece has jumped 2 squares, it means the computer has
# taken an opponents' piece.
if abs(r(1)-r(3)) == 2 then
s((r(1)+r(3))/2,(r(2)+r(4))/2)=0 # Delete the opponent's piece
# See if we can jump again. Evaluate all possible moves.
x=r(3)
y=r(4)
for a=-2 to 2 step 4
if s(x,y)=-1 then
b=-2
gosub 1370
endif
if s(x,y)=-2 then
for b=-2 to 2 step 4
gosub 1370
next b
endif
next a
# If we've found a move, go back and make that one as well
if r(0) <> -99 then
print "TO" r(3); r(4);
r(0)=-99
goto 1240
endif
goto 1420 # Skip the sub
# If (u,v) is in the bounds, evaluate it as a move using
# the sub at 910
1370 Sub_Start
u=x+a
v=y+b
if u<0 or u>7 or v<0 or v>7 then 1400
if s(u,v)=0 and s(x+a/2,y+b/2)>0 then gosub 910
1400 return
1420 endif
# Now, print the board
print
print
print
for y=7 to 0 step-1
for x=0 to 7
i=5*x
print tab(i);
if s(x,y)=0 then print".";
if s(x,y)=1 then print"O";
if s(x,y)=-1 then print"X";
if s(x,y)=-2 then print"X*";
if s(x,y)=2 then print"O*";
next x
print" "
print
next y
print
# Check if either player is out of pieces. If so, announce the
# winner.
for l=0 to 7
for m=0 to 7
if s(l,m)=1 or s(l,m)=2 then z=1
if s(l,m)=-1 or s(l,m)=-2 then t=1
next m
next l
if z<>1 then 1885
if t<>1 then 1880
# Prompt the player for their move.
z=0
t=0
1590 input "FROM";e,h
x=e
y=h
if s(x,y)<=0 then 1590
1670 input "TO";a,b
x=a
y=b
if s(x,y)=0 and abs(a-e)<=2 and abs(a-e)=abs(b-h)then 1700
print chr$(7)chr$(11); # bell, vertical tab; invalid move
goto 1670
1700 i=46 # Not used; probably a bug
1750 loop
# Make the move and stop unless it might be a jump.
s(a,b) = s(e,h)
s(e,h) = 0
if abs(e-a) <> 2 then break
# Remove the piece jumped over
s((e+a)/2,(h+b)/2) = 0
# Prompt for another move; -1 means player can't, so I've won.
# Keep prompting until there's a valid move or the player gives
# up.
1802 input "+TO";a1,b1
if a1 < 0 then break
if s(a1,b1) <> 0 or abs(a1-a) <>2 or abs(b1-b) <> 2 then 1802
# Update the move variables to correspond to the next jump
e=a
h=b
a=a1
b=b1
i=i+15 # Not used; probably a bug
endloop
# If the player has reached the end of the board, crown this piece
1810 if b=7 then s(a,b)=2
# And play the next turn.
goto 230
# Endgame:
1880 print
print "YOU WIN."
end
1885 print
print "I WIN."
end

82
23_Checkers/checkers.bas Normal file
View File

@@ -0,0 +1,82 @@
5 PRINT TAB(32);"CHECKERS"
10 PRINT TAB(15);"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"
15 PRINT:PRINT:PRINT
20 PRINT "THIS IS THE GAME OF CHECKERS. THE COMPUTER IS X,"
25 PRINT "AND YOU ARE O. THE COMPUTER WILL MOVE FIRST."
30 PRINT "SQUARES ARE REFERRED TO BY A COORDINATE SYSTEM."
35 PRINT "(0,0) IS THE LOWER LEFT CORNER"
40 PRINT "(0,7) IS THE UPPER LEFT CORNER"
45 PRINT "(7,0) IS THE LOWER RIGHT CORNER"
50 PRINT "(7,7) IS THE UPPER RIGHT CORNER"
55 PRINT "THE COMPUTER WILL TYPE '+TO' WHEN YOU HAVE ANOTHER"
60 PRINT "JUMP. TYPE TWO NEGATIVE NUMBERS IF YOU CANNOT JUMP."
65 PRINT:PRINT:PRINT
80 DIM R(4),S(7,7):G=-1:R(0)=-99
90 DATA 1,0,1,0,0,0,-1,0,0,1,0,0,0,-1,0,-1,15
120 FOR X=0 TO 7:FOR Y=0 TO 7:READ J:IF J=15 THEN 180
160 S(X,Y)=J:GOTO 200
180 RESTORE:READ S(X,Y)
200 NEXT Y,X
230 FOR X=0 TO 7:FOR Y=0 TO 7:IF S(X,Y)>-1 THEN 350
310 IF S(X,Y)=-1 THEN FOR A=-1 TO 1 STEP 2:B=G:GOSUB 650:NEXT A
330 IF S(X,Y)=-2 THEN FOR A=-1 TO 1 STEP 2:FOR B=-1 TO 1 STEP 2:GOSUB 650:NEXT B,A
350 NEXT Y,X:GOTO 1140
650 U=X+A:V=Y+B:IF U<0 OR U>7 OR V<0 OR V>7 THEN 870
740 IF S(U,V)=0 THEN GOSUB 910:GOTO 870
770 IF S(U,V)<0 THEN 870
790 U=U+A:V=V+B:IF U<0 OR V<0 OR U>7 OR V>7 THEN 870
850 IF S(U,V)=0 THEN GOSUB 910
870 RETURN
910 IF V=0 AND S(X,Y)=-1 THEN Q=Q+2
920 IF ABS(Y-V)=2 THEN Q=Q+5
960 IF Y=7 THEN Q=Q-2
980 IF U=0 OR U=7 THEN Q=Q+1
1030 FOR C=-1 TO 1 STEP 2:IF U+C<0 OR U+C>7 OR V+G<0 THEN 1080
1035 IF S(U+C,V+G)<0 THEN Q=Q+1:GOTO 1080
1040 IF U-C<0 OR U-C>7 OR V-G>7 THEN 1080
1045 IF S(U+C,V+G)>0 AND(S(U-C,V-G)=0 OR(U-C=X AND V-G=Y))THEN Q=Q-2
1080 NEXT C:IF Q>R(0)THEN R(0)=Q:R(1)=X:R(2)=Y:R(3)=U:R(4)=V
1100 Q=0:RETURN
1140 IF R(0)=-99 THEN 1880
1230 PRINT CHR$(30)"FROM"R(1);R(2)"TO"R(3);R(4);:R(0)=-99
1240 IF R(4)=0 THEN S(R(3),R(4))=-2:GOTO 1420
1250 S(R(3),R(4))=S(R(1),R(2))
1310 S(R(1),R(2))=0:IF ABS(R(1)-R(3))<>2 THEN 1420
1330 S((R(1)+R(3))/2,(R(2)+R(4))/2)=0
1340 X=R(3):Y=R(4):IF S(X,Y)=-1 THEN B=-2:FOR A=-2 TO 2 STEP 4:GOSUB 1370
1350 IF S(X,Y)=-2 THEN FOR A=-2 TO 2 STEP 4:FOR B=-2 TO 2 STEP 4:GOSUB 1370:NEXT B
1360 NEXT A:IF R(0)<>-99 THEN PRINT"TO"R(3);R(4);:R(0)=-99:GOTO 1240
1365 GOTO 1420
1370 U=X+A:V=Y+B:IF U<0 OR U>7 OR V<0 OR V>7 THEN 1400
1380 IF S(U,V)=0 AND S(X+A/2,Y+B/2)>0 THEN GOSUB 910
1400 RETURN
1420 PRINT:PRINT:PRINT:FOR Y=7 TO 0 STEP-1:FOR X=0 TO 7:I=5*X:PRINT TAB(I);
1430 IF S(X,Y)=0 THEN PRINT".";
1470 IF S(X,Y)=1 THEN PRINT"O";
1490 IF S(X,Y)=-1 THEN PRINT"X";
1510 IF S(X,Y)=-2 THEN PRINT"X*";
1530 IF S(X,Y)=2 THEN PRINT"O*";
1550 NEXT X:PRINT" ":PRINT:NEXT Y:PRINT
1552 FOR L=0 TO 7
1554 FOR M=0 TO 7
1556 IF S(L,M)=1 OR S(L,M)=2 THEN Z=1
1558 IF S(L,M)=-1 OR S(L,M)=-2 THEN T=1
1560 NEXT M
1562 NEXT L
1564 IF Z<>1 THEN 1885
1566 IF T<>1 THEN 1880
1570 Z=0: T=0
1590 INPUT "FROM";E,H:X=E:Y=H:IF S(X,Y)<=0 THEN 1590
1670 INPUT "TO";A,B:X=A:Y=B
1680 IF S(X,Y)=0 AND ABS(A-E)<=2 AND ABS(A-E)=ABS(B-H)THEN 1700
1690 PRINT CHR$(7)CHR$(11);:GOTO 1670
1700 I=46
1750 S(A,B)=S(E,H):S(E,H)=0:IF ABS(E-A)<>2 THEN 1810
1800 S((E+A)/2,(H+B)/2)=0
1802 INPUT "+TO";A1,B1:IF A1<0 THEN 1810
1804 IF S(A1,B1)<>0 OR ABS(A1-A)<>2 OR ABS(B1-B)<>2 THEN 1802
1806 E=A:H=B:A=A1:B=B1:I=I+15:GOTO 1750
1810 IF B=7 THEN S(A,B)=2
1830 GOTO 230
1880 PRINT: PRINT "YOU WIN.": END
1885 PRINT: PRINT "I WIN.": END

View 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/)

View 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/)

View 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)

View File

@@ -0,0 +1,9 @@
<html>
<head>
<title>CHECKERS</title>
</head>
<body>
<pre id="output" style="font-size: 12pt;"></pre>
<script src="checkers.js"></script>
</body>
</html>

View File

@@ -0,0 +1,300 @@
// CHECKERS
//
// 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;
}
// x,y = origin square
// a,b = movement direction
function try_computer()
{
u = x + a;
v = y + b;
if (u < 0 || u > 7 || v < 0 || v > 7)
return;
if (s[u][v] == 0) {
eval_move();
return;
}
if (s[u][v] < 0) // Cannot jump over own pieces
return;
u += a;
u += b;
if (u < 0 || u > 7 || v < 0 || v > 7)
return;
if (s[u][v] == 0)
eval_move();
}
// x,y = origin square
// u,v = target square
function eval_move()
{
if (v == 0 && s[x][y] == -1)
q += 2;
if (Math.abs(y - v) == 2)
q += 5;
if (y == 7)
q -= 2;
if (u == 0 || u == 7)
q++;
for (c = -1; c <= 1; c += 2) {
if (u + c < 0 || u + c > 7 || v + g < 0)
continue;
if (s[u + c][v + g] < 0) { // Computer piece
q++;
continue;
}
if (u - c < 0 || u - c > 7 || v - g > 7)
continue;
if (s[u + c][v + g] > 0 && (s[u - c][v - g] == 0 || (u - c == x && v - g == y)))
q -= 2;
}
if (q > r[0]) { // Best movement so far?
r[0] = q; // Take note of score
r[1] = x; // Origin square
r[2] = y;
r[3] = u; // Target square
r[4] = v;
}
q = 0;
}
function more_captures() {
u = x + a;
v = y + b;
if (u < 0 || u > 7 || v < 0 || v > 7)
return;
if (s[u][v] == 0 && s[x + a / 2][y + b / 2] > 0)
eval_move();
}
var r = [-99, 0, 0, 0, 0];
var s = [];
for (x = 0; x <= 7; x++)
s[x] = [];
var g = -1;
var data = [1, 0, 1, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, -1, 0, -1, 15];
var p = 0;
var q = 0;
// Main program
async function main()
{
print(tab(32) + "CHECKERS\n");
print(tab(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n");
print("\n");
print("\n");
print("\n");
print("THIS IS THE GAME OF CHECKERS. THE COMPUTER IS X,\n");
print("AND YOU ARE O. THE COMPUTER WILL MOVE FIRST.\n");
print("SQUARES ARE REFERRED TO BY A COORDINATE SYSTEM.\n");
print("(0,0) IS THE LOWER LEFT CORNER\n");
print("(0,7) IS THE UPPER LEFT CORNER\n");
print("(7,0) IS THE LOWER RIGHT CORNER\n");
print("(7,7) IS THE UPPER RIGHT CORNER\n");
print("THE COMPUTER WILL TYPE '+TO' WHEN YOU HAVE ANOTHER\n");
print("JUMP. TYPE TWO NEGATIVE NUMBERS IF YOU CANNOT JUMP.\n");
print("\n");
print("\n");
print("\n");
for (x = 0; x <= 7; x++) {
for (y = 0; y <= 7; y++) {
if (data[p] == 15)
p = 0;
s[x][y] = data[p];
p++;
}
}
while (1) {
// Search the board for the best movement
for (x = 0; x <= 7; x++) {
for (y = 0; y <= 7; y++) {
if (s[x][y] > -1)
continue;
if (s[x][y] == -1) { // Piece
for (a = -1; a <= 1; a += 2) {
b = g; // Only advances
try_computer();
}
} else if (s[x][y] == -2) { // King
for (a = -1; a <= 1; a += 2) {
for (b = -1; b <= 1; b += 2) {
try_computer();
}
}
}
}
}
if (r[0] == -99) {
print("\n");
print("YOU WIN.\n");
break;
}
print("FROM " + r[1] + "," + r[2] + " TO " + r[3] + "," + r[4]);
r[0] = -99;
while (1) {
if (r[4] == 0) { // Computer reaches the bottom
s[r[3]][r[4]] = -2; // King
break;
}
s[r[3]][r[4]] = s[r[1]][r[2]]; // Move
s[r[1]][r[2]] = 0;
if (Math.abs(r[1] - r[3]) == 2) {
s[(r[1] + r[3]) / 2][(r[2] + r[4]) / 2] = 0; // Capture
x = r[3];
y = r[4];
if (s[x][y] == -1) {
b = -2;
for (a = -2; a <= 2; a += 4) {
more_captures();
}
} else if (s[x][y] == -2) {
for (a = -2; a <= 2; a += 4) {
for (b = -2; b <= 2; b += 4) {
more_captures();
}
}
}
if (r[0] != -99) {
print(" TO " + r[3] + "," + r[4]);
r[0] = -99;
continue;
}
}
break;
}
print("\n");
print("\n");
print("\n");
for (y = 7; y >= 0; y--) {
str = "";
for (x = 0; x <= 7; x++) {
if (s[x][y] == 0)
str += ".";
if (s[x][y] == 1)
str += "O";
if (s[x][y] == -1)
str += "X";
if (s[x][y] == -2)
str += "X*";
if (s[x][y] == 2)
str += "O*";
while (str.length % 5)
str += " ";
}
print(str + "\n");
print("\n");
}
print("\n");
z = 0;
t = 0;
for (l = 0; l <= 7; l++) {
for (m = 0; m <= 7; m++) {
if (s[l][m] == 1 || s[l][m] == 2)
z = 1;
if (s[l][m] == -1 || s[l][m] == -2)
t = 1;
}
}
if (z != 1) {
print("\n");
print("I WIN.\n");
break;
}
if (t != 1) {
print("\n");
print("YOU WIN.\n");
break;
}
do {
print("FROM");
e = await input();
h = parseInt(e.substr(e.indexOf(",") + 1));
e = parseInt(e);
x = e;
y = h;
} while (s[x][y] <= 0) ;
do {
print("TO");
a = await input();
b = parseInt(a.substr(a.indexOf(",") + 1));
a = parseInt(a);
x = a;
y = b;
if (s[x][y] == 0 && Math.abs(a - e) <= 2 && Math.abs(a - e) == Math.abs(b - h))
break;
print("WHAT?\n");
} while (1) ;
i = 46;
do {
s[a][b] = s[e][h]
s[e][h] = 0;
if (Math.abs(e - a) != 2)
break;
s[(e + a) / 2][(h + b) / 2] = 0;
while (1) {
print("+TO");
a1 = await input();
b1 = parseInt(a1.substr(a1.indexOf(",") + 1));
a1 = parseInt(a1);
if (a1 < 0)
break;
if (s[a1][b1] == 0 && Math.abs(a1 - a) == 2 && Math.abs(b1 - b) == 2)
break;
}
if (a1 < 0)
break;
e = a;
h = b;
a = a1;
b = b1;
i += 15;
} while (1);
if (b == 7) // Player reaches top
s[a][b] = 2; // Convert to king
}
}
main();

View 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))

View 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/)

View 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/)

View File

@@ -0,0 +1,398 @@
"""
CHECKERS
How about a nice game of checkers?
Ported by Dave LeCompte
"""
import collections
PAGE_WIDTH = 64
HUMAN_PLAYER = 1
COMPUTER_PLAYER = -1
HUMAN_PIECE = 1
HUMAN_KING = 2
COMPUTER_PIECE = -1
COMPUTER_KING = -2
EMPTY_SPACE = 0
TOP_ROW = 7
BOTTOM_ROW = 0
MoveRecord = collections.namedtuple(
"MoveRecord", ["quality", "start_x", "start_y", "dest_x", "dest_y"]
)
def print_centered(msg):
spaces = " " * ((PAGE_WIDTH - len(msg)) // 2)
print(spaces + msg)
def print_header(title):
print_centered(title)
print_centered("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY")
print()
print()
print()
def get_coordinates(prompt):
err_msg = "ENTER COORDINATES in X,Y FORMAT"
while True:
print(prompt)
response = input()
if "," not in response:
print(err_msg)
continue
try:
x, y = [int(c) for c in response.split(",")]
except ValueError as ve:
print(err_msg)
continue
return x, y
def is_legal_board_coordinate(x, y):
return (0 <= x <= 7) and (0 <= y <= 7)
class Board:
def __init__(self):
self.spaces = [[0 for y in range(8)] for x in range(8)]
for x in range(8):
if (x % 2) == 0:
self.spaces[x][6] = COMPUTER_PIECE
self.spaces[x][2] = HUMAN_PIECE
self.spaces[x][0] = HUMAN_PIECE
else:
self.spaces[x][7] = COMPUTER_PIECE
self.spaces[x][5] = COMPUTER_PIECE
self.spaces[x][1] = HUMAN_PIECE
def __str__(self):
pieces = {
EMPTY_SPACE: ".",
HUMAN_PIECE: "O",
HUMAN_KING: "O*",
COMPUTER_PIECE: "X",
COMPUTER_KING: "X*",
}
s = "\n\n\n"
for y in range(7, -1, -1):
for x in range(0, 8):
piece_str = pieces[self.spaces[x][y]]
piece_str += " " * (5 - len(piece_str))
s += piece_str
s += "\n"
s += "\n\n"
return s
def get_spaces(self):
for x in range(0, 8):
for y in range(0, 8):
yield x, y
def get_spaces_with_computer_pieces(self):
for x, y in self.get_spaces():
contents = self.spaces[x][y]
if contents < 0:
yield x, y
def get_spaces_with_human_pieces(self):
for x, y in self.get_spaces():
contents = self.spaces[x][y]
if contents > 0:
yield x, y
def get_legal_deltas_for_space(self, x, y):
contents = self.spaces[x][y]
if contents == COMPUTER_PIECE:
for delta_x in (-1, 1):
yield (delta_x, -1)
else:
for delta_x in (-1, 1):
for delta_y in (-1, 1):
yield (delta_x, delta_y)
def pick_computer_move(self):
move_record = None
for start_x, start_y in self.get_spaces_with_computer_pieces():
for delta_x, delta_y in self.get_legal_deltas_for_space(start_x, start_y):
new_move_record = self.check_move(start_x, start_y, delta_x, delta_y)
if new_move_record is None:
continue
if (move_record is None) or (
new_move_record.quality > move_record.quality
):
move_record = new_move_record
return move_record
def check_move(self, start_x, start_y, delta_x, delta_y):
new_x = start_x + delta_x
new_y = start_y + delta_y
if not is_legal_board_coordinate(new_x, new_y):
return None
contents = self.spaces[new_x][new_y]
if contents == EMPTY_SPACE:
return self.evaluate_move(start_x, start_y, new_x, new_y)
if contents < 0:
return None
# check jump landing space, which is an additional dx, dy from new_x, newy
landing_x = new_x + delta_x
landing_y = new_y + delta_y
if not is_legal_board_coordinate(landing_x, landing_y):
return None
if self.spaces[landing_x][landing_y] == EMPTY_SPACE:
return self.evaluate_move(start_x, start_y, landing_x, landing_y)
def evaluate_move(self, start_x, start_y, dest_x, dest_y):
quality = 0
if dest_y == 0 and self.spaces[start_x][start_y] == COMPUTER_PIECE:
# promoting is good
quality += 2
if abs(dest_y - start_y) == 2:
# jumps are good
quality += 5
if start_y == 7:
# prefer to defend back row
quality -= 2
if dest_x in (0, 7):
# moving to edge column
quality += 1
for delta_x in (-1, 1):
if not is_legal_board_coordinate(dest_x + delta_x, dest_y - 1):
continue
if self.spaces[dest_x + delta_x][dest_y - 1] < 0:
# moving into "shadow" of another computer piece
quality += 1
if not is_legal_board_coordinate(dest_x - delta_x, dest_y + 1):
continue
if (
(self.spaces[dest_x + delta_x][dest_y - 1] > 0)
and (self.spaces[dest_x - delta_x][dest_y + 1] == EMPTY_SPACE)
or ((dest_x - delta_x == start_x) and (dest_y + 1 == start_y))
):
# we are moving up to a human checker that could jump us
quality -= 2
return MoveRecord(quality, start_x, start_y, dest_x, dest_y)
def remove_r_pieces(self, move_record):
self.remove_pieces(
move_record.start_x,
move_record.start_y,
move_record.dest_x,
move_record.dest_y,
)
def remove_pieces(self, start_x, start_y, dest_x, dest_y):
self.spaces[dest_x][dest_y] = self.spaces[start_x][start_y]
self.spaces[start_x][start_y] = EMPTY_SPACE
if abs(dest_x - start_x) == 2:
mid_x = (start_x + dest_x) // 2
mid_y = (start_y + dest_y) // 2
self.spaces[mid_x][mid_y] = EMPTY_SPACE
def play_computer_move(self, move_record):
print(
f"FROM {move_record.start_x} {move_record.start_y} TO {move_record.dest_x} {move_record.dest_y}"
)
while True:
if move_record.dest_y == BOTTOM_ROW:
# KING ME
self.remove_r_pieces(move_record)
self.spaces[move_record.dest_x][move_record.dest_y] = COMPUTER_KING
return
else:
self.spaces[move_record.dest_x][move_record.dest_y] = self.spaces[
move_record.start_x
][move_record.start_y]
self.remove_r_pieces(move_record)
if abs(move_record.dest_x - move_record.start_x) != 2:
return
landing_x = move_record.dest_x
landing_y = move_record.dest_y
best_move = None
if self.spaces[landing_x][landing_y] == COMPUTER_PIECE:
for delta_x in (-2, 2):
test_record = self.try_extend(landing_x, landing_y, delta_x, -2)
if not (move_record is None):
if (best_move is None) or (
move_record.quality > best_move.quality
):
best_move = test_record
else:
assert self.spaces[landing_x][landing_y] == COMPUTER_KING
for delta_x in (-2, 2):
for delta_y in (-2, 2):
test_record = self.try_extend(
landing_x, landing_y, delta_x, delta_y
)
if not (move_record is None):
if (best_move is None) or (
move_record.quality > best_move.quality
):
best_move = test_record
if best_move is None:
return
else:
print(f"TO {best_move.dest_x} {best_move.dest_y}")
move_record = best_move
def try_extend(self, start_x, start_y, delta_x, delta_y):
new_x = start_x + delta_x
new_y = start_y + delta_y
if not is_legal_board_coordinate(new_x, new_y):
return None
jumped_x = start_x + delta_x // 2
jumped_y = start_y + delta_y // 2
if (self.spaces[new_x][new_y] == EMPTY_SPACE) and (
self.spaces[jumped_x][jumped_y] > 0
):
return self.evaluate_move(start_x, start_y, new_x, new_y)
def get_human_move(self):
is_king = False
while True:
start_x, start_y = get_coordinates("FROM?")
if self.spaces[start_x][start_y] > 0:
break
is_king = self.spaces[start_x][start_y] == HUMAN_KING
while True:
dest_x, dest_y = get_coordinates("TO?")
if (not is_king) and (dest_y < start_y):
# CHEATER! Trying to move non-king backwards
continue
if (
(self.spaces[dest_x][dest_y] == 0)
and (abs(dest_x - start_x) <= 2)
and (abs(dest_x - start_x) == abs(dest_y - start_y))
):
break
return start_x, start_y, dest_x, dest_y
def get_human_extension(self, start_x, start_y):
is_king = self.spaces[start_x][start_y] == HUMAN_KING
while True:
dest_x, dest_y = get_coordinates("+TO?")
if dest_x < 0:
return False, None
if (not is_king) and (dest_y < start_y):
# CHEATER! Trying to move non-king backwards
continue
if (
(self.spaces[dest_x][dest_y] == EMPTY_SPACE)
and (abs(dest_x - start_x) == 2)
and (abs(dest_y - start_y) == 2)
):
return True, (start_x, start_y, dest_x, dest_y)
def play_human_move(self, start_x, start_y, dest_x, dest_y):
self.remove_pieces(start_x, start_y, dest_x, dest_y)
if dest_y == TOP_ROW:
# KING ME
self.spaces[dest_x][dest_y] = HUMAN_KING
def check_pieces(self):
if len(list(self.get_spaces_with_computer_pieces())) == 0:
print_human_won()
return False
if len(list(self.get_spaces_with_computer_pieces())) == 0:
print_computer_won()
return False
return True
def print_instructions():
print("THIS IS THE GAME OF CHECKERS. THE COMPUTER IS X,")
print("AND YOU ARE O. THE COMPUTER WILL MOVE FIRST.")
print("SQUARES ARE REFERRED TO BY A COORDINATE SYSTEM.")
print("(0,0) IS THE LOWER LEFT CORNER")
print("(0,7) IS THE UPPER LEFT CORNER")
print("(7,0) IS THE LOWER RIGHT CORNER")
print("(7,7) IS THE UPPER RIGHT CORNER")
print("THE COMPUTER WILL TYPE '+TO' WHEN YOU HAVE ANOTHER")
print("JUMP. TYPE TWO NEGATIVE NUMBERS IF YOU CANNOT JUMP.")
print()
print()
print()
def print_human_won():
print()
print("YOU WIN.")
def print_computer_won():
print()
print("I WIN.")
def play_game():
board = Board()
while True:
move_record = board.pick_computer_move()
if move_record is None:
print_human_won()
return
board.play_computer_move(move_record)
print(board)
if not board.check_pieces():
return
start_x, start_y, dest_x, dest_y = board.get_human_move()
board.play_human_move(start_x, start_y, dest_x, dest_y)
if abs(dest_x - start_x) == 2:
while True:
extend, move = board.get_human_extension(dest_x, dest_y)
if not extend:
break
start_x, start_y, dest_x, dest_y = move
board.play_human_move(start_x, start_y, dest_x, dest_y)
def main():
print_header("CHECKERS")
print_instructions()
play_game()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,8 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Ruby](https://www.ruby-lang.org/en/)
This version preserves the underlying algorithms and functionality of
the original while using more modern programming constructs
(functions, classes, symbols) and providing much more detailed
comments. It also fixes some (but not all) of the bugs.

View File

@@ -0,0 +1,651 @@
#!/usr/bin/env ruby
# Checkers in Ruby, Version 1
#
# This version of the game attempts to preserve the underlying
# algorithm(s) and feel of the BASIC version while using more modern
# coding techniques. Specifically:
#
# 1. The major data structures (the board and current move, known as S
# and R in the BASIC version) have been turned into classes. In
# addition, I made a class for coordinates so that you don't always
# have to deal with pairs of numbers.
#
# 2. Much of the functionality associated with this data has been moved
# into methods of these classes in line with the philosophy that objects
# are smart data.
#
# 3. While I've kept the board as a single object (not global, though),
# this program will create many Move objects (i.e. copies of the move
# under consideration) rather than operating on a single global
# instance.
#
# 4. The rest of the code has been extracted into Ruby functions with
# all variables as local as reasonably possible.
#
# 5. Pieces are now represented with Symbols instead of integers; this
# made it *much* easier to understand what was going on.
#
# 6. There are various internal sanity checks. They fail by throwing
# a string as an exception. (This is generally frowned upon if
# you're going to catch the exception later but we never do that;
# an exception here means a bug in the software and the way to fix
# that is to fix the program.)
#
# And probably other stuff.
#
# Note: I've ordered the various major definitions here from (roughly)
# general to specific so that if you read the code starting from the
# beginning, you'll (hopefully) get a big-picture view first and then
# get into details. Normally, I'd order things by topic and define
# things before using them, which is a better ordering. So in this
# case, do what I say and not what I do.
#
# Some global constants
#
BOARD_TEXT_INDENT = 30 # Number of spaces to indent the board when printing
# Various constants related to the game of Checkers.
#
# (Yes, they're obvious but if you see BOARD_WIDTH, you know this is
# related to board dimensions in a way that you wouldn't if you saw
# '8'.)
BOARD_WIDTH = 8
KING_ROW_X = 0
KING_ROW_Y = BOARD_WIDTH - 1
# This is the mainline routine of the program. Ruby doesn't require
# that you put this in a function but this way, your local variables
# are contained here. It's also neater, IMO.
#
# The name 'main' isn't special; it's just a function. The last line
# of this program is a call to it.
def main
print <<EOF
Checkers
Inspiration: Creative Computing Morristown, New Jersey
This is the game of checkers. The computer is X,
and you are O. The computer will move first.
Squares are referred to by a coordinate system.
(0,0) is the lower left corner
(0,7) is the upper left corner
(7,0) is the lower right corner
(7,7) is the upper right corner
The computer will tell you when you have another
jump.
Enter a blank line if you cannot make a move. If this
not after a jump, it means you have lost the game.
EOF
board = Board.new
winner = game_loop(board)
puts board.text
puts
puts "#{winner ? 'I' : 'You'} win."
end
# This is the main game loop. Returns true if I win and false if the
# player wins. Each of my_turn and players_turn return false if they
# could not move. (Recall that the game ends when one player can't
# move.)
def game_loop(board)
while true
my_turn(board) or return false
players_turn(board) or return true
end
end
#
# My (i.e. computer's) turn
#
# Play my turn, returning true if it could make at least one move and
# false otherwise. This is pretty simple because the heavy lifting is
# done by the methods of the Board and Move classes.
def my_turn(board, jumpStart = nil)
# Print the initial board
puts board.text
# Find the candidate move. If this is a move after a jump, the
# starting point will be in jumpStart and we use only that.
# Otherwise, we check all available pieces.
bestMove = Move.invalid
candidates = jumpStart ? [jumpStart] : board.my_pieces
for coord in candidates
bestMove = bestMove.betterOf( board.bestMoveFrom(coord, !!jumpStart) )
end
# If we can't find a move, we're done
if !bestMove.valid?
puts "I can't make another #{jumpStart ? 'jump' : 'move'}."
return false
end
# Do the move
puts "My move: #{bestMove}"
canMoveAgain = board.make_move!(bestMove)
# Repeat (recursively) if we can make another jump
my_turn(board, bestMove.to) if canMoveAgain
# No loss yet!
return true
end
#
# Player's turn
#
# Prompt the player for a move and then play it. If the player can
# make multiple moves (i.e. jump), repeat. Return true if at least one
# move was played, false otherwise.
#
# Note that this does not enforce all of the rules of the game; it
# assumes the player knows the rules and will stick to them. In
# particular, the player is not required to jump if possible. This
# behaviour was inherited from the BASIC version but I've made some
# improvements so we will catch most illegal moves; in that case, the
# player is prompted for another move.
def players_turn(board)
from = nil
while true
move = get_player_move(board, from)
# If the player declines to enter a move, we're done. If this is
# the first move ('from' will be nil in this case), it's a forfeit
# and we return false to indicate the player has lost. But if
# it's a jump, the player was able to move and therefor has not
# lost so we return true. (Note: this requires the player to not
# cheat, which is kind of a bug but that's how the original worked
# as well.)
return false if !from && !move
return true if from && !move
canMoveAgain = board.make_move!(move)
return true unless canMoveAgain
# If the player can jump again, repeat from the new position.
from = move.to
end
# Can't reach here
end
# Prompt the player for a move, read it, check if it's legal and
# return it if it is or try again if it isn't. If the player declines
# to move, returns nil. If this is a jump following a previous jump,
# the second argument will be the source (i.e. Move.from) and we don't
# ask for it here..
def get_player_move(board, jumpFrom = nil)
puts "You can jump again!" if jumpFrom
while true
puts board.text
puts "Enter your move:"
puts "From? #{jumpFrom}" if jumpFrom
from = jumpFrom || read_coord("From? ")
to = read_coord("To? ") unless !from
# If the player entered a blank line, it's a forfeit
if !from || !to
return nil if jumpFrom || confirm_quit()
end
move = Move.new(from, to)
return move if board.legal_move?(false, move)
print "\nThis move is not legal!\n\n"
end
end
# Prompt the player for the x,y coordinates of a piece on the board,
# repeating until a valid coordinate is provided and return it. If
# the player enters a blank line, returns nil; this is interpreted as
# declining to move.
def read_coord(prompt)
while true
print prompt
STDOUT.flush
line = (STDIN.gets || "").strip
return nil if line == ''
coord = parse_coord(line)
return coord if coord
puts "Invalid input; try again!"
end
end
# Ask the player if they wish to quit; return true if they do, false
# otherwise. Tends to assume false unless given an explicit yes.
def confirm_quit
print "Really forfeit (y/n) "
STDOUT.flush
line = STDIN.gets.strip
return !!(line =~ /^y(es)?$/i) # For extra credit, explain why I use '!!'
end
# Parse a string containing x,y coordinates to produce and return a
# new Coord object. Returns nil if the string is not a valid
# coordinate.
def parse_coord(coord)
coord = coord.gsub(/\s/, '')
return nil unless coord =~ /^\d,\d$/
parts = coord.split(/,/)
result = Coord.new(parts[0].to_i, parts[1].to_i)
return nil unless result.valid?
return result
end
#
# Classes
#
# Class to represent the game board and all of the pieces on it.
#
# Like the BASIC version, the board is represented using an 8 by 8
# array. However, instead of using numbers between -2 and 2, we
# represent board squares with Ruby symbols:
#
# - :_ (underscore) is an empty square
# - :o and :x (lowercase) are ordinary pieces for their respective sides
# - :O and :X (uppercase) are kings for their respective sides
#
# Most of the smarts of the game are also here as methods of this
# class. So my_turn() doesn't (e.g.) go through the board squares
# looking for the best move; it asks the board for it by calling
# bestMove().
#
class Board
# Set up the board to the starting position
def initialize
@board = []
col1 = %i{o _ o _ _ _ x _}
col2 = %i{_ o _ _ _ x _ x}
4.times {
@board += [col1.dup, col2.dup]
}
end
# We use [] and []= (the array/hash get and set operators) to get
# individual board pieces instead of accessing it directly; this
# prevents errors where you get the row and column wrong in some
# places and not others. They're private because it turns out that
# nothing outside of this class needs them but we could make them
# public without a lot of trouble. The position must be a Coord
# object.
private
def [](coord)
return nil unless coord.valid?
return @board[coord.x][coord.y]
end
def []=(coord, value)
# Sanity checks
raise "Invalid coordinate: #{coord}" unless coord.valid?
raise "Invalid board value: #{value}" unless %i{_ x o X O}.include?(value)
@board[coord.x][coord.y] = value
end
public
# Return an ASCII picture of the board. This is what gets printed
# between turns.
def text(indent = BOARD_TEXT_INDENT)
result = ""
glyph = {_: '.'} # Replace some symbols when printing for readability
for y in (0 .. 7).to_a.reverse
result += ' '*indent + y.to_s + ' '
for x in (0 .. 7)
result += glyph.fetch(self[ Coord.new(x, y) ]) {|k| k.to_s}
end
result += "\n"
end
result += ' '*indent + ' ' + (0..7).map{|n| n.to_s}.join
return result + "\n\n"
end
# Answer various questions about positions on the board
def mine_at?(coord)
return %i{x X}.include? self[coord]
end
def king_at?(coord)
return %i{X O}.include? self[coord]
end
def opponent_at?(coord)
return %i{o O}.include? self[coord]
end
def empty_at?(coord)
return self[coord] == :_
end
# Return a list of all valid positions on the board
def all_coords
coords = []
for x in 0 .. BOARD_WIDTH - 1
for y in 0 .. BOARD_WIDTH - 1
coords.push Coord.new(x, y)
end
end
return coords
end
# Return a list of all coords containing my (i.e. X's) pieces.
def my_pieces
all_coords.select{|coord| mine_at?(coord)}
end
private
# Return a list of all valid positions on the board. (To do: skip
# all light-coloured squares, since they will never hold a piece.)
def all_coords
coords = []
for x in 0 .. BOARD_WIDTH - 1
for y in 0 .. BOARD_WIDTH - 1
coords.push Coord.new(x, y)
end
end
return coords
end
public
# Test if the move 'move' is legal; tests for me (i.e. the computer)
# if 'is_me' is true and for the human player if 'is_me' is false.
def legal_move?(is_me, move)
# If the move isn't valid, it's not legal.
return false unless move.valid?
# There's a piece belonging to me at the source
return false unless mine_at?(move.from) == is_me
# The destination is empty
return false unless empty_at?(move.to)
# The source holds one of the players' pieces
return false if empty_at?(move.from) || mine_at?(move.from) != is_me
# moving forward if not a king
if !king_at?(move.from)
return false if is_me && move.to.y > move.from.y
return false if !is_me && move.to.y < move.from.y
end
# If jumping, that there's an opponent's piece between the start
# and end.
return false if
move.jump? &&
(empty_at?(move.midpoint) || opponent_at?(move.midpoint) != is_me)
# Otherwise, it's legal
return true
end
# Perform 'move' on the board. 'move' must be legal; the player
# performing it is determined by the move's starting ('from')
# position.
def make_move!(move)
piece = self[move.from]
# Sanity check
raise "Illegal move: #{move}" unless legal_move?(piece.downcase == :x,move)
# Promote the piece if it's reached the end row
piece = piece.upcase if
(piece == :x && move.to.y == KING_ROW_X) ||
(piece == :o && move.to.y == KING_ROW_Y)
# And do the move
self[move.to] = piece
self[move.from] = :_
# Remove the piece jumped over if this is a jump
self[move.midpoint] = :_ if move.jump?
end
# Return the best (i.e. likely to win) move possible for the
# piece (mine) at 'coord'.
def bestMoveFrom(coord, mustJump)
so_far = Move.invalid
return so_far unless coord.valid?
offsets = [ [-1, -1], [1, -1]]
offsets += [ [-1, 1], [1, 1]] if king_at?(coord)
for ofx, ofy in offsets
new_coord = coord.by(ofx, ofy)
if opponent_at?(new_coord)
new_coord = new_coord.by(ofx, ofy)
elsif mustJump
next
end
next unless new_coord.valid?
so_far = so_far.betterOf( ratedMove(coord, new_coord) )
end
return so_far
end
# Create and return a move for *me* from Coords 'from' to 'to' with
# its 'rating' set to how good the move looks according to criteria
# used by the BASIC version of this program. If the move is
# illegal, returns an invalid Move object.
def ratedMove(from, to)
return Move.invalid unless legal_move?(true, Move.new(from, to))
rating = 0
# +2 if it promotes this piece
rating += 2 if to.y == 0
# +50 if it takes the opponent's piece. (Captures are mandatory
# so we ensure that a capture will always outrank a non-capture.)
rating += 4 if (from.y - to.y).abs == 2
# -2 if we're moving away from the king row
rating -= 2 if from.y == BOARD_WIDTH - 1
# +1 for putting the piece against a vertical boundary
rating += 1 if to.x == 0 || to.x == BOARD_WIDTH - 1
# +1 for each friendly piece behind this one
[-1, 1].each {|c|
rating += 1 if mine_at?( to.by(c, -1) )
}
# -2 for each opponent's piece that can now capture this one.
# (This includes a piece that may be captured when moving here;
# this is a bug.)
[ -1, 1].each {|c|
there = to.by(c, -1)
opposite = to.by(-c, 1)
rating -= 2 if
opponent_at?(there) && (empty_at?(opposite) || opposite == from)
}
return Move.new(from, to, rating)
end
end
# Class to hold the X and Y coordinates of a position on the board.
#
# Coord objects are immutable--that is, they never change after
# creation. Instead, you will always get a modified copy back.
class Coord
# Coordinates are readable
attr_reader :x, :y
# Initialize
def initialize(x, y)
@x, @y = [x,y]
end
# Test if this move is on the board.
def valid?
return x >= 0 && y >= 0 && x < BOARD_WIDTH && y < BOARD_WIDTH
end
# Test if this Coord is equal to another Coord.
def ==(other)
return other.class == self.class && other.x == @x && other.y == y
end
# Return a string that describes this Coord in a human-friendly way.
def to_s
return "(#{@x},#{@y})"
end
# Return a new Coord whose x and y coordinates have been adjusted by
# arguments 'x' and 'y'.
def by(x, y)
return Coord.new(@x + x, @y + y)
end
end
# Class to represent a move by a player between two positions,
# possibly with a rating that can be used to select the best of a
# collection of moves.
#
# An (intentionally) invalid move will have a value of nil for both
# 'from' and 'to'. Most methods other than 'valid?' assume the Move
# is valid.
class Move
# Readable fields:
attr_reader :from, :to, :rating
# The initializer; -99 is the lowest rating from the BASIC version
# so we use that here as well.
def initialize(from, to, rating = -99)
@from, @to, @rating = [from, to, rating]
# Sanity check; the only invalid Move tolerated is the official
# one (i.e. with nil for each endpoint.)
raise "Malformed Move: #{self}" if @from && @to && !valid?
end
# Return an invalid Move object.
def self.invalid
return self.new(nil, nil, -99)
end
# Return true if this is a valid move (i.e. as close to legal as we
# can determine without seeing the board.)
def valid?
# Not valid if @from or @to is nil
return false unless @from && @to
# Not valid unless both endpoints are on the board
return false unless @from.valid? && @to.valid?
# Not valid unless it's a diagonal move by 1 or 2 squares
dx, dy = delta
return false if dx.abs != dy.abs || (dx.abs != 1 && dx.abs != 2)
# Otherwise, valid
return true
end
# Return true if this move is a jump, false otherwise
def jump?
return valid? && magnitude() == 2
end
# Return the coordinates of the piece being jumped over by this
# move.
def midpoint
raise "Called 'midpoint' on a non-jump move!" unless jump?
dx, dy = delta
return @from.by(dx / dx.abs, dy / dy.abs)
end
# Return the better-rated of self or otherMove.
def betterOf(otherMove)
return otherMove if !valid?
return rating > otherMove.rating ? self : otherMove
end
# Return a human-friendly string representing this move.
def to_s
return "[NOMOVE]" if !@from && !@to # Well-known invalid move
jumpover = jump? ?
"-> #{midpoint} ->"
: "->"
return "#{@from} #{jumpover} #{to}#{valid? ? '' : ' [INVALID]'}"
end
private
# Return the distance (x, y) between the 'from' and 'to' locations.
def delta
return [to.x - from.x, to.y - from.y]
end
# Return the number of squares this move will take the piece (either
# 1 or 2).
def magnitude
# Note: we assume that this move is a legal move (and therefore
# diagonal); otherwise, this may not be correct.
return (to.x - from.x).abs
end
end
# Start the game
main()

View 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)