mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-01-16 23:12:08 -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:
18
23_Checkers/README.md
Normal file
18
23_Checkers/README.md
Normal 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)
|
||||
315
23_Checkers/checkers.annotated.bas
Normal file
315
23_Checkers/checkers.annotated.bas
Normal 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
82
23_Checkers/checkers.bas
Normal 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
|
||||
3
23_Checkers/csharp/README.md
Normal file
3
23_Checkers/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/)
|
||||
3
23_Checkers/java/README.md
Normal file
3
23_Checkers/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
23_Checkers/javascript/README.md
Normal file
3
23_Checkers/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
23_Checkers/javascript/checkers.html
Normal file
9
23_Checkers/javascript/checkers.html
Normal 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>
|
||||
300
23_Checkers/javascript/checkers.js
Normal file
300
23_Checkers/javascript/checkers.js
Normal 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();
|
||||
3
23_Checkers/pascal/README.md
Normal file
3
23_Checkers/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
23_Checkers/perl/README.md
Normal file
3
23_Checkers/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
23_Checkers/python/README.md
Normal file
3
23_Checkers/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/)
|
||||
398
23_Checkers/python/checkers.py
Normal file
398
23_Checkers/python/checkers.py
Normal 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()
|
||||
8
23_Checkers/ruby/README.md
Normal file
8
23_Checkers/ruby/README.md
Normal 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.
|
||||
651
23_Checkers/ruby/checkers.rb
Normal file
651
23_Checkers/ruby/checkers.rb
Normal 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()
|
||||
3
23_Checkers/vbnet/README.md
Normal file
3
23_Checkers/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