From a0c2565adaa6dfc2d81de4771174f17edb4ce5f3 Mon Sep 17 00:00:00 2001 From: Claus Volko <49327712+adokhugi@users.noreply.github.com> Date: Mon, 17 Jan 2022 17:17:34 +0100 Subject: [PATCH 01/12] Kotlin implementation of the Acey Ducey game --- 01_Acey_Ducey/aceyducey.kt | 74 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 01_Acey_Ducey/aceyducey.kt diff --git a/01_Acey_Ducey/aceyducey.kt b/01_Acey_Ducey/aceyducey.kt new file mode 100644 index 00000000..ccc5eb0a --- /dev/null +++ b/01_Acey_Ducey/aceyducey.kt @@ -0,0 +1,74 @@ +import java.util.Random + +fun printCard(a: Int) { + if (a < 11) println(a) + if (a == 11) println("JACK") + if (a == 12) println("QUEEN") + if (a == 13) println("KING") + if (a == 14) println("ACE") +} + +fun main() { + println("ACEY DUCEY CARD GAME") + println("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + println() + println() + println("ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER ") + println("THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP") + println("YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING") + println("ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE") + println("A VALUE BETWEEN THE FIRST TWO.") + println("IF YOU DO NOT WANT TO BET, INPUT A 0") + var random = Random() + do { + var q = 100 + var a : Int + var b : Int + var m : Int + println("YOU NOW HAVE " + q + " DOLLARS.") + println() + do { + do { + do { + println("HERE ARE YOUR NEXT TWO CARDS: ") + do { + a = random.nextInt(12) + 2 + b = random.nextInt(12) + 2 + } while (a >= b); + printCard(a) + printCard(b) + println() + println() + print("WHAT IS YOUR BET") + m = readLine()!!.toInt() + if (m == 0) { + println("CHICKEN!!") + println() + } + } while (m == 0); + if (m > q) { + println("SORRY, MY FRIEND, BUT YOU BET TOO MUCH.") + println("YOU HAVE ONLY " + q + " DOLLARS TO BET.") + } + } while (m > q); + var c = random.nextInt(12) + 2 + printCard(c) + println() + if (c > a && c < b) { + println("YOU WIN!!!") + q += m + } + else { + println("SORRY, YOU LOSE") + if (m < q) q -= m + } + } while (m < q); + println() + println() + println("SORRY, FRIEND, BUT YOU BLEW YOUR WAD.") + println() + println() + println("TRY AGAIN (YES OR NO)") + } while (readLine() == "YES"); + println("O.K., HOPE YOU HAD FUN!") +} From d05bdd13e7e9680cc945e02df1e73847100fefd0 Mon Sep 17 00:00:00 2001 From: Noah Pauls Date: Mon, 17 Jan 2022 18:06:22 -0800 Subject: [PATCH 02/12] completed csharp version --- 88_3-D_Tic-Tac-Toe/csharp/Program.cs | 10 + 88_3-D_Tic-Tac-Toe/csharp/Qubic.cs | 1178 ++++++++++++++++++++++++ 88_3-D_Tic-Tac-Toe/csharp/QubicData.cs | 559 +++++++++++ 3 files changed, 1747 insertions(+) create mode 100644 88_3-D_Tic-Tac-Toe/csharp/Program.cs create mode 100644 88_3-D_Tic-Tac-Toe/csharp/Qubic.cs create mode 100644 88_3-D_Tic-Tac-Toe/csharp/QubicData.cs diff --git a/88_3-D_Tic-Tac-Toe/csharp/Program.cs b/88_3-D_Tic-Tac-Toe/csharp/Program.cs new file mode 100644 index 00000000..12c6ca83 --- /dev/null +++ b/88_3-D_Tic-Tac-Toe/csharp/Program.cs @@ -0,0 +1,10 @@ +namespace ThreeDTicTacToe +{ + class Program + { + static void Main() + { + new Qubic().Run(); + } + } +} diff --git a/88_3-D_Tic-Tac-Toe/csharp/Qubic.cs b/88_3-D_Tic-Tac-Toe/csharp/Qubic.cs new file mode 100644 index 00000000..595db661 --- /dev/null +++ b/88_3-D_Tic-Tac-Toe/csharp/Qubic.cs @@ -0,0 +1,1178 @@ +using System.Text; + +namespace ThreeDTicTacToe +{ + /// + /// Qubic is a 3D Tic-Tac-Toe game played on a 4x4x4 cube. This code allows + /// a player to compete against a deterministic AI that is surprisingly + /// difficult to beat. + /// + internal class Qubic + { + // The Y variable in the original BASIC. + private static readonly int[] CornersAndCenters = QubicData.CornersAndCenters; + // The M variable in the original BASIC. + private static readonly int[,] RowsByPlane = QubicData.RowsByPlane; + + // Board spaces are filled in with numeric values. A space could be: + // + // - EMPTY: no one has moved here yet. + // - PLAYER: the player moved here. + // - MACHINE: the machine moved here. + // - POTENTIAL: the machine, in the middle of its move, + // might fill a space with a potential move marker, which + // prioritizes the space once it finally chooses where to move. + // + // The numeric values allow the program to determine what moves have + // been made in a row by summing the values in a row. In theory, the + // individual values could be any positive numbers that satisfy the + // following: + // + // - EMPTY = 0 + // - POTENTIAL * 4 < PLAYER + // - PLAYER * 4 < MACHINE + private const double PLAYER = 1.0; + private const double MACHINE = 5.0; + private const double POTENTIAL = 0.125; + private const double EMPTY = 0.0; + + // The X variable in the original BASIC. This is the Qubic board, + // flattened into a 1D array. + private readonly double[] Board = new double[64]; + + // The L variable in the original BASIC. There are 76 unique winning rows + // in the board, so each gets an entry in RowSums. A row sum can be used + // to check what moves have been made to that row in the board. + // + // Example: if RowSums[i] == PLAYER * 4, the player won with row i! + private readonly double[] RowSums = new double[76]; + + public Qubic() { } + + /// + /// Run the Qubic game. + /// + /// Show the title, prompt for instructions, then begin the game loop. + /// + public void Run() + { + Title(); + Instructions(); + Loop(); + } + + /*********************************************************************** + /* Terminal Text/Prompts + /**********************************************************************/ + #region TerminalText + + /// + /// Display title and attribution. + /// + /// Original BASIC: 50-120 + /// + private static void Title() + { + Console.WriteLine( + "\n" + + " QUBIC\n\n" + + " CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n" + ); + } + + /// + /// Prompt user for game instructions. + /// + /// Original BASIC: 210-313 + /// + private static void Instructions() + { + Console.Write("DO YOU WANT INSTRUCTIONS? "); + var yes = ReadYesNo(); + + if (yes) + { + Console.WriteLine( + "\n" + + "THE GAME IS TIC-TAC-TOE IN A 4 X 4 X 4 CUBE.\n" + + "EACH MOVE IS INDICATED BY A 3 DIGIT NUMBER, WITH EACH\n" + + "DIGIT BETWEEN 1 AND 4 INCLUSIVE. THE DIGITS INDICATE THE\n" + + "LEVEL, ROW, AND COLUMN, RESPECTIVELY, OF THE OCCUPIED\n" + + "PLACE.\n" + + "\n" + + "TO PRINT THE PLAYING BOARD, TYPE 0 (ZERO) AS YOUR MOVE.\n" + + "THE PROGRAM WILL PRINT THE BOARD WITH YOUR MOVES INDI-\n" + + "CATED WITH A (Y), THE MACHINE'S MOVES WITH AN (M), AND\n" + + "UNUSED SQUARES WITH A ( ). OUTPUT IS ON PAPER.\n" + + "\n" + + "TO STOP THE PROGRAM RUN, TYPE 1 AS YOUR MOVE.\n\n" + ); + } + } + + /// + /// Prompt player for whether they would like to move first, or allow + /// the machine to make the first move. + /// + /// Original BASIC: 440-490 + /// + /// true if the player wants to move first + private static bool PlayerMovePreference() + { + Console.Write("DO YOU WANT TO MOVE FIRST? "); + var result = ReadYesNo(); + Console.WriteLine(); + return result; + } + + /// + /// Run the Qubic program loop. + /// + private void Loop() + { + // The "retry" loop; ends if player quits or chooses not to retry + // after game ends. + while (true) + { + ClearBoard(); + var playerNext = PlayerMovePreference(); + + // The "game" loop; ends if player quits, player/machine wins, + // or game ends in draw. + while (true) + { + if (playerNext) + { + // Player makes a move. + var playerAction = PlayerMove(); + if (playerAction == PlayerAction.Move) + { + playerNext = !playerNext; + } + else + { + return; + } + } + else + { + // Check for wins, if any. + RefreshRowSums(); + if (CheckPlayerWin() || CheckMachineWin()) + { + break; + } + + // Machine makes a move. + var machineAction = MachineMove(); + if (machineAction == MachineAction.Move) + { + playerNext = !playerNext; + } + else if (machineAction == MachineAction.End) + { + break; + } + else + { + throw new Exception("unreachable; machine should always move or end game in game loop"); + } + } + } + + var retry = RetryPrompt(); + + if (!retry) + { + return; + } + } + } + + /// + /// Prompt the user to try another game. + /// + /// Original BASIC: 1490-1560 + /// + /// true if the user wants to play again + private static bool RetryPrompt() + { + Console.Write("DO YOU WANT TO TRY ANOTHER GAME? "); + return ReadYesNo(); + } + + /// + /// Read a yes/no from the terminal. This method accepts anything that + /// starts with N/n as no and Y/y as yes. + /// + /// true if the player answered yes + private static bool ReadYesNo() + { + while (true) + { + var response = Console.ReadLine() ?? " "; + if (response.ToLower().StartsWith("y")) + { + return true; + } + else if (response.ToLower().StartsWith("n")) + { + return false; + } + else + { + Console.Write("INCORRECT ANSWER. PLEASE TYPE 'YES' OR 'NO'. "); + } + } + } + + #endregion + + /*********************************************************************** + /* Player Move + /**********************************************************************/ + #region PlayerMove + + /// + /// Possible actions player has taken after ending their move. This + /// replaces the `GOTO` logic that allowed the player to jump out of + /// the game loop and quit. + /// + private enum PlayerAction + { + /// + /// The player ends the game prematurely. + /// + Quit, + /// + /// The player makes a move on the board. + /// + Move, + } + + /// + /// Make the player's move based on their input. + /// + /// Original BASIC: 500-620 + /// + /// Whether the player moved or quit the program. + private PlayerAction PlayerMove() + { + // Loop until a valid move is inputted. + while (true) + { + var move = ReadMove(); + if (move == 1) + { + return PlayerAction.Quit; + } + else if (move == 0) + { + ShowBoard(); + } + else + { + ClearPotentialMoves(); + if (TryCoordToIndex(move, out int moveIndex)) + { + if (Board[moveIndex] == EMPTY) + { + Board[moveIndex] = PLAYER; + return PlayerAction.Move; + } + else + { + Console.WriteLine("THAT SQUARE IS USED, TRY AGAIN."); + } + } + else + { + Console.WriteLine("INCORRECT MOVE, TRY AGAIN."); + } + } + } + } + + /// + /// Read a player move from the terminal. Move can be any integer. + /// + /// Original BASIC: 510-520 + /// + /// the move inputted + private static int ReadMove() + { + Console.Write("YOUR MOVE? "); + return ReadInteger(); + } + + /// + /// Read an integer from the terminal. + /// + /// Original BASIC: 520 + /// + /// Unlike the basic, this code will not accept any string that starts + /// with a number; only full number strings are allowed. + /// + /// the integer inputted + private static int ReadInteger() + { + while (true) + { + var response = Console.ReadLine() ?? " "; + + if (int.TryParse(response, out var move)) + { + return move; + + } + else + { + Console.Write("!NUMBER EXPECTED - RETRY INPUT LINE--? "); + } + } + } + + /// + /// Display the board to the player. Spaces taken by the player are + /// marked with "Y", while machine spaces are marked with "M". + /// + /// Original BASIC: 2550-2740 + /// + private void ShowBoard() + { + var s = new StringBuilder(new string('\n', 9)); + + for (int i = 1; i <= 4; i++) + { + for (int j = 1; j <= 4; j++) + { + s.Append(' ', 3 * (j + 1)); + for (int k = 1; k <= 4; k++) + { + int q = (16 * i) + (4 * j) + k - 21; + s.Append(Board[q] switch + { + EMPTY or POTENTIAL => "( ) ", + PLAYER => "(Y) ", + MACHINE => "(M) ", + _ => throw new Exception($"invalid space value {Board[q]}"), + }); + } + s.Append("\n\n"); + } + s.Append("\n\n"); + } + + Console.WriteLine(s.ToString()); + } + + #endregion + + /*********************************************************************** + /* Machine Move + /**********************************************************************/ + #region MachineMove + + /// + /// Check all rows for a player win. + /// + /// A row indicates a player win if its sum = PLAYER * 4. + /// + /// Original BASIC: 720-780 + /// + /// whether the player won in any row + private bool CheckPlayerWin() + { + for (int row = 0; row < 76; row++) + { + if (RowSums[row] == (PLAYER * 4)) + { + // Found player win! + Console.WriteLine("YOU WIN AS FOLLOWS"); + DisplayRow(row); + return true; + } + } + + // No player win found. + return false; + } + + /// + /// Check all rows for a row that the machine could move to to win + /// immediately. + /// + /// A row indicates a player could win immediately if it has three + /// machine moves already; that is, sum = MACHINE * 3. + /// + /// Original Basic: 790-920 + /// + /// + private bool CheckMachineWin() + { + for (int row = 0; row < 76; row++) + { + if (RowSums[row] == (MACHINE * 3)) + { + // Found a winning row! + for (int space = 0; space < 4; space++) + { + int move = RowsByPlane[row, space]; + if (Board[move] == EMPTY) + { + // Found empty space in winning row; move there. + Board[move] = MACHINE; + Console.WriteLine($"MACHINE MOVES TO {IndexToCoord(move)} , AND WINS AS FOLLOWS"); + DisplayRow(row); + return true; + } + } + } + } + + // No winning row available. + return false; + } + + /// + /// Display the coordinates of a winning row. + /// + /// index into RowsByPlane data + private void DisplayRow(int row) + { + for (int space = 0; space < 4; space++) + { + Console.Write($" {IndexToCoord(RowsByPlane[row, space])} "); + } + Console.WriteLine(); + } + + /// + /// Possible actions machine can take in a move. This helps replace the + /// complex GOTO logic from the original BASIC, which allowed the + /// program to jump from the machine's action to the end of the game. + /// + private enum MachineAction + { + /// + /// Machine did not take any action. + /// + None, + /// + /// Machine made a move. + /// + Move, + /// + /// Machine either won, conceded, or found a draw. + /// + End, + } + + /// + /// Machine decides where to move on the board, and ends the game if + /// appropriate. + /// + /// The machine's AI tries to take the following actions (in order): + /// + /// 1. If the player has a row that will get them the win on their + /// next turn, block that row. + /// 2. If the machine can trap the player (create two different rows + /// with three machine moves each that cannot be blocked by only a + /// single player move, create such a trap. + /// 3. If the player can create a similar trap for the machine on + /// their next move, block the space where that trap would be + /// created. + /// 4. Find a plane in the board that is well-populated by player + /// moves, and take a space in the first such plane. + /// 5. Find the first open corner or center and move there. + /// 6. Find the first open space and move there. + /// + /// If none of these actions are possible, then the board is entirely + /// full, and the game results in a draw. + /// + /// Original BASIC: start at 930 + /// + /// the action the machine took + private MachineAction MachineMove() + { + // The actions the machine attempts to take, in order. + var actions = new Func[] + { + BlockPlayer, + MakePlayerTrap, + BlockMachineTrap, + MoveByPlane, + MoveCornerOrCenter, + MoveAnyOpenSpace, + }; + + foreach (var action in actions) + { + // Try each action, moving to the next if nothing happens. + var actionResult = action(); + if (actionResult != MachineAction.None) + { + // Not in original BASIC: check for draw after each machine + // move. + if (CheckDraw()) + { + return DrawGame(); + } + return actionResult; + } + } + + // If we got here, all spaces are taken. Draw the game. + return DrawGame(); + } + + /// + /// Block a row with three spaces already taken by the player. + /// + /// Original BASIC: 930-1010 + /// + /// + /// Move if the machine blocked, + /// None otherwise + /// + private MachineAction BlockPlayer() + { + for (int row = 0; row < 76; row++) + { + if (RowSums[row] == (PLAYER * 3)) + { + // Found a row to block on! + for (int space = 0; space < 4; space++) + { + if (Board[RowsByPlane[row, space]] == EMPTY) + { + // Take the remaining empty space. + Board[RowsByPlane[row, space]] = MACHINE; + Console.WriteLine($"NICE TRY. MACHINE MOVES TO {IndexToCoord(RowsByPlane[row, space])}"); + return MachineAction.Move; + } + } + } + } + + // Didn't find a row to block on. + return MachineAction.None; + } + + /// + /// Create a trap for the player if possible. A trap can be created if + /// moving to a space on the board results in two different rows having + /// three MACHINE spaces, with the remaining space not shared between + /// the two rows. The player can only block one of these traps, so the + /// machine will win. + /// + /// If a player trap is not possible, but a row is found that is + /// particularly advantageous for the machine to move to, the machine + /// will try and move to a corner-edge in that row. + /// + /// Original BASIC: 1300-1480 + /// + /// + /// Move if a trap was created, + /// End if the machine conceded, + /// None otherwise + /// + private MachineAction MakePlayerTrap() + { + for (int row = 0; row < 76; row++) + { + // Refresh row sum, since new POTENTIALs might have changed it. + var rowSum = RefreshRowSum(row); + + // Machine has moved in this row twice, and player has not moved + // in this row. + if (rowSum >= (MACHINE * 2) && rowSum < (MACHINE * 2) + 1) + { + // Machine has no potential moves yet in this row. + if (rowSum == (MACHINE * 2)) + { + for (int space = 0; space < 4; space++) + { + // Empty space can potentially be used to create a + // trap. + if (Board[RowsByPlane[row, space]] == EMPTY) + { + Board[RowsByPlane[row, space]] = POTENTIAL; + } + } + } + // Machine has already found a potential move in this row, + // so a trap can be created with another row. + else + { + return MakeOrBlockTrap(row); + } + } + } + + // No player traps can be made. + RefreshRowSums(); + + for (int row = 0; row < 76; row++) + { + // A row may be particularly advantageous for the machine to + // move to at this point; this is the case if a row is entirely + // filled with POTENTIAL or has one MACHINE and others + // POTENTIAL. + if (RowSums[row] == (POTENTIAL * 4) || RowSums[row] == MACHINE + (POTENTIAL * 3)) + { + // Try moving to a corner-edge in an advantageous row. + return MoveCornerEdges(row, POTENTIAL); + } + } + + // No spaces found that are particularly advantageous to machine. + ClearPotentialMoves(); + return MachineAction.None; + } + + /// + /// Block a trap that the player could create for the machine on their + /// next turn. + /// + /// If there are no player traps to block, but a row is found that is + /// particularly advantageous for the player to move to, the machine + /// will try and move to a corner-edge in that row. + /// + /// Original BASIC: 1030-1190 + /// + /// + /// Move if a trap was created, + /// End if the machine conceded, + /// None otherwise + /// + private MachineAction BlockMachineTrap() + { + for (int i = 0; i < 76; i++) + { + // Refresh row sum, since new POTENTIALs might have changed it. + var rowSum = RefreshRowSum(i); + + // Player has moved in this row twice, and machine has not moved + // in this row. + if (rowSum >= (PLAYER * 2) && rowSum < (PLAYER * 2) + 1) + { + // Machine has no potential moves yet in this row. + if (rowSum == (PLAYER * 2)) + { + for (int j = 0; j < 4; j++) + { + if (Board[RowsByPlane[i, j]] == EMPTY) + { + Board[RowsByPlane[i, j]] = POTENTIAL; + } + } + } + // Machine has already found a potential move in this row, + // so a trap can be created with another row by the player. + // Move to block. + else + { + return MakeOrBlockTrap(i); + } + } + } + + // No player traps to block found. + RefreshRowSums(); + + for (int row = 0; row < 76; row++) + { + // A row may be particularly advantageous for the player to move + // to at this point, indicated by a row containing all POTENTIAL + // moves or one PLAYER and rest POTENTIAL. + if (RowSums[row] == (POTENTIAL * 4) || RowSums[row] == PLAYER + (POTENTIAL * 3)) + { + // Try moving to a corner-edge in an advantageous row. + return MoveCornerEdges(row, POTENTIAL); + } + } + + // No spaces found that are particularly advantageous to the player. + return MachineAction.None; + } + + /// + /// Either make a trap for the player or block a trap the player could + /// create on their next turn. + /// + /// Unclear how this method could possibly end with a concession; it + /// seems it can only be called if the row contains a potential move. + /// + /// Original BASIC: 2230-2350 + /// + /// the row containing the space to move to + /// + /// Move if the machine moved, + /// End if the machine conceded + /// + private MachineAction MakeOrBlockTrap(int row) + { + for (int space = 0; space < 4; space++) + { + if (Board[RowsByPlane[row, space]] == POTENTIAL) + { + Board[RowsByPlane[row, space]] = MACHINE; + + // Row sum indicates we're blocking a player trap. + if (RowSums[row] < MACHINE) + { + Console.Write("YOU FOX. JUST IN THE NICK OF TIME, "); + } + // Row sum indicates we're completing a machine trap. + else + { + Console.Write("LET'S SEE YOU GET OUT OF THIS: "); + } + + Console.WriteLine($"MACHINE MOVES TO {IndexToCoord(RowsByPlane[row, space])}"); + + return MachineAction.Move; + } + } + + // Unclear how this can be reached. + Console.WriteLine("MACHINE CONCEDES THIS GAME."); + return MachineAction.End; + } + + /// + /// Find a satisfactory plane on the board and move to one if that + /// plane's corner-edges. + /// + /// A plane on the board is satisfactory if it meets the following + /// conditions: + /// 1. Player has made exactly 4 moves on the plane. + /// 2. Machine has made either 0 or one moves on the plane. + /// The machine then attempts to move to a corner-edge in that plane, + /// first finding any potential moves from the previous action it took + /// and moving there, and moving to any open corner-edge if there are + /// no potential moves found. + /// + /// This action by the machine tries to prevent the player from having + /// exclusive control over any plane in the board. + /// + /// Original BASIC: 1830-2020 + /// + /// The BASIC code for this action is tightly bound with base code for + /// MoveCornerEdges. + /// + /// + /// Move if a move in a plane was found, + /// None otherwise + /// + private MachineAction MoveByPlane() + { + // For each plane in the cube... + for (int plane = 1; plane <= 18; plane++) + { + double planeSum = PlaneSum(plane); + + // Check that plane sum satisfies condition. + const double P4 = PLAYER * 4; + const double P4_M1 = (PLAYER * 4) + MACHINE; + if ( + (planeSum >= P4 && planeSum < P4 + 1) || + (planeSum >= P4_M1 && planeSum < P4_M1 + 1) + ) + { + // Try to move to corner edges in each row of plane + // First, check for corner edges marked as POTENTIAL. + for (int row = (4 * plane) - 4; row < (4 * plane); row++) + { + var moveResult = MoveCornerEdges(row, POTENTIAL); + if (moveResult != MachineAction.None) + { + return moveResult; + } + } + + // If no POTENTIAL corner-edge found, look for an EMPTY one. + for (int row = (4 * plane) - 4; row < (4 * plane); row++) + { + var moveResult = MoveCornerEdges(row, EMPTY); + if (moveResult != MachineAction.None) + { + return moveResult; + } + } + } + } + + // No good corner edges found by plane. + ClearPotentialMoves(); + return MachineAction.None; + } + + /// + /// Given a row, move to the first corner-edge of the cube that has the + /// given value. + /// + /// Corner edges are pieces of the cube that have two faces. The AI + /// prefers to move to these spaces before others, presumably + /// because they are powerful moves: a corner edge space is contained + /// in 3 rows on the cube. + /// + /// Original BASIC: 2360-2490 + /// + /// In the original BASIC, this code is pointed to from three different + /// locations by GOTOs (1440/50, or MakePlayerTrap; 1160/70, or + /// BlockMachineTrap; and 1990, or MoveByPlane). Interestingly, line + /// 2440 can only be reached if the code proceeds after a call from + /// 1990. In short, this means that the code flow is incredibly + /// difficult to understand; the block of code at 2360 acts like a + /// generalized subroutine, but contains bits of code that belong + /// to specific pieces of calling code. + /// + /// the row to try to move to + /// + /// what value the space to move to should have in Board + /// + /// + /// Move if a corner-edge piece in the row with the given spaceValue was + /// found, + /// None otherwise + /// + private MachineAction MoveCornerEdges(int row, double spaceValue) + { + // Given a row, we want to find the corner-edge pieces in that row. + // We know that each row is part of a plane, and that the first + // and last rows of the plane are on the plane edge, while the + // other two rows are in the middle. If we know whether a row is an + // edge or middle, we can determine which spaces in that row are + // corner-edges. + // + // Below is a birds-eye view of a plane in the cube, with rows + // oriented horizontally: + // + // row 0: ( ) (1) (2) ( ) + // row 1: (0) ( ) ( ) (3) + // row 2: (0) ( ) ( ) (3) + // row 3: ( ) (1) (2) ( ) + // + // The corner edge pieces have their row indices marked. The pattern + // above shows that: + // + // if row == 0 | 3, corner edge spaces = [1, 2] + // if row == 1 | 2, corner edge spaces = [0, 3] + + // The below condition replaces the following BASIC code (2370): + // + // I-(INT(I/4)*4)>1 + // + // which in C# would be: + // + // + // int a = i - (i / 4) * 4 <= 1) + // ? 1 + // : 2; + // + // In the above, i is the one-indexed row in RowsByPlane. + // + // This condition selects a different a value based on whether the + // given row is on the edge or middle of its plane. + int a = (row % 4) switch + { + 0 or 3 => 1, // row is on edge of plane + 1 or 2 => 2, // row is in middle of plane + _ => throw new Exception($"unreachable ({row % 4})"), + }; + + // Iterate through corner edge pieces of the row. + // + // if a = 1 (row is edge), iterate through [0, 3] + // if a = 2 (row is middle), iterate through [1, 2] + for (int space = a - 1; space <= 4 - a; space += 5 - (2 * a)) + { + if (Board[RowsByPlane[row, space]] == spaceValue) + { + // Found a corner-edge to take! + Board[RowsByPlane[row, space]] = MACHINE; + Console.WriteLine($"MACHINE TAKES {IndexToCoord(RowsByPlane[row, space])}"); + return MachineAction.Move; + } + } + + // No valid corner edge to take. + return MachineAction.None; + } + + /// + /// Find the first open corner or center in the board and move there. + /// + /// Original BASIC: 1200-1290 + /// + /// This is the only place where the Z variable from the BASIC code is + /// used; here it is implied in the for loop. + /// + /// + /// Move if an open corner/center was found and moved to, + /// None otherwise + /// + private MachineAction MoveCornerOrCenter() + { + foreach (int space in CornersAndCenters) + { + if (Board[space] == EMPTY) + { + Board[space] = MACHINE; + Console.WriteLine($"MACHINE MOVES TO {IndexToCoord(space)}"); + return MachineAction.Move; + } + } + + return MachineAction.None; + } + + /// + /// Find the first open space in the board and move there. + /// + /// Original BASIC: 1720-1800 + /// + /// + /// Move if an open space was found and moved to, + /// None otherwise + /// + private MachineAction MoveAnyOpenSpace() + { + for (int row = 0; row < 64; row++) + { + if (Board[row] == EMPTY) + { + Board[row] = MACHINE; + Console.WriteLine($"MACHINE LIKES {IndexToCoord(row)}"); + return MachineAction.Move; + } + } + return MachineAction.None; + } + + /// + /// Draw the game in the event that there are no open spaces. + /// + /// Original BASIC: 1810-1820 + /// + /// End + private MachineAction DrawGame() + { + Console.WriteLine("THIS GAME IS A DRAW."); + return MachineAction.End; + } + + #endregion + + /*********************************************************************** + /* Helpers + /**********************************************************************/ + #region Helpers + + /// + /// Attempt to transform a cube coordinate to an index into Board. + /// + /// A valid cube coordinate is a three-digit number, where each digit + /// of the number X satisfies 1 <= X <= 4. + /// + /// Examples: + /// 111 -> 0 + /// 444 -> 63 + /// 232 -> 35 + /// + /// If the coord provided is not valid, the transformation fails. + /// + /// The conversion from coordinate to index is essentially a conversion + /// between base 4 and base 10. + /// + /// Original BASIC: 525-580 + /// + /// This method fixes a bug in the original BASIC (525-526), which only + /// checked whether the given coord satisfied 111 <= coord <= 444. This + /// allows invalid coordinates such as 199 and 437, whose individual + /// digits are out of range. + /// + /// cube coordinate (e.g. "111", "342") + /// trasnformation output + /// + /// true if the transformation was successful, false otherwise + /// + private static bool TryCoordToIndex(int coord, out int index) + { + // parse individual digits, subtract 1 to get base 4 number + var hundreds = (coord / 100) - 1; + var tens = ((coord % 100) / 10) - 1; + var ones = (coord % 10) - 1; + + // bounds check for each digit + foreach (int digit in new int[] { hundreds, tens, ones }) + { + if (digit < 0 || digit > 3) + { + index = -1; + return false; + } + } + + // conversion from base 4 to base 10 + index = (16 * hundreds) + (4 * tens) + ones; + return true; + } + + /// + /// Transform a Board index into a valid cube coordinate. + /// + /// Examples: + /// 0 -> 111 + /// 63 -> 444 + /// 35 -> 232 + /// + /// The conversion from index to coordinate is essentially a conversion + /// between base 10 and base 4. + /// + /// Original BASIC: 1570-1610 + /// + /// Board index + /// the corresponding cube coordinate + private static int IndexToCoord(int index) + { + // check that index is valid + if (index < 0 || index > 63) + { + // runtime exception; all uses of this method are with + // indices provided by the program, so this should never fail + throw new Exception($"index {index} is out of range"); + } + + // convert to base 4, add 1 to get cube coordinate + var hundreds = (index / 16) + 1; + var tens = ((index % 16) / 4) + 1; + var ones = (index % 4) + 1; + + // concatenate digits + int coord = (hundreds * 100) + (tens * 10) + ones; + return coord; + } + + /// + /// Refresh the values in RowSums to account for any changes. + /// + /// Original BASIC: 1640-1710 + /// + private void RefreshRowSums() + { + for (var row = 0; row < 76; row++) + { + RefreshRowSum(row); + } + } + + /// + /// Refresh a row in RowSums to reflect changes. + /// + /// row in RowSums to refresh + /// row sum after refresh + private double RefreshRowSum(int row) + { + double rowSum = 0; + for (int space = 0; space < 4; space++) + { + rowSum += Board[RowsByPlane[row, space]]; + } + RowSums[row] = rowSum; + return rowSum; + } + + /// + /// Calculate the sum of spaces in one of the 18 cube planes in RowSums. + /// + /// Original BASIC: 1840-1890 + /// + /// the desired plane + /// sum of spaces in plane + private double PlaneSum(int plane) + { + double planeSum = 0; + for (int row = (4 * (plane - 1)); row < (4 * plane); row++) + { + for (int space = 0; space < 4; space++) + { + planeSum += Board[RowsByPlane[row, space]]; + } + } + return planeSum; + } + + /// + /// Check whether the board is in a draw state, that is all spaces are + /// full and neither the player nor the machine has won. + /// + /// The original BASIC contains a bug that if the player moves first, a + /// draw will go undetected. An example series of player inputs + /// resulting in such a draw (assuming player goes first): + /// + /// 114, 414, 144, 444, 122, 221, 112, 121, + /// 424, 332, 324, 421, 231, 232, 244, 311, + /// 333, 423, 331, 134, 241, 243, 143, 413, + /// 142, 212, 314, 341, 432, 412, 431, 442 + /// + /// whether the game is a draw + private bool CheckDraw() + { + for (var i = 0; i < 64; i++) + { + if (Board[i] != PLAYER && Board[i] != MACHINE) + { + return false; + } + } + + RefreshRowSums(); + + for (int row = 0; row < 76; row++) + { + var rowSum = RowSums[row]; + if (rowSum == PLAYER * 4 || rowSum == MACHINE * 4) + { + return false; + } + } + + + return true; + } + + /// + /// Reset POTENTIAL spaces in Board to EMPTY. + /// + /// Original BASIC: 2500-2540 + /// + private void ClearPotentialMoves() + { + for (var i = 0; i < 64; i++) + { + if (Board[i] == POTENTIAL) + { + Board[i] = EMPTY; + } + } + } + + /// + /// Reset all spaces in Board to EMPTY. + /// + /// Original BASIC: 400-420 + /// + private void ClearBoard() + { + for (var i = 0; i < 64; i++) + { + Board[i] = EMPTY; + } + } + + #endregion + } +} diff --git a/88_3-D_Tic-Tac-Toe/csharp/QubicData.cs b/88_3-D_Tic-Tac-Toe/csharp/QubicData.cs new file mode 100644 index 00000000..298ee933 --- /dev/null +++ b/88_3-D_Tic-Tac-Toe/csharp/QubicData.cs @@ -0,0 +1,559 @@ +namespace ThreeDTicTacToe +{ + /// + /// Data in this class was originally given by the following DATA section in + /// the BASIC program: + /// + /// 2030 DATA 1,49,52,4,13,61,64,16,22,39,23,38,26,42,27,43 + /// 2040 DATA 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 + /// 2050 DATA 21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38 + /// 2060 DATA 39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56 + /// 2070 DATA 57,58,59,60,61,62,63,64 + /// 2080 DATA 1,17,33,49,5,21,37,53,9,25,41,57,13,29,45,61 + /// 2090 DATA 2,18,34,50,6,22,38,54,10,26,42,58,14,30,46,62 + /// 2100 DATA 3,19,35,51,7,23,39,55,11,27,43,59,15,31,47,63 + /// 2110 DATA 4,20,36,52,8,24,40,56,12,28,44,60,16,32,48,64 + /// 2120 DATA 1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61 + /// 2130 DATA 2,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62 + /// 2140 DATA 3,7,11,15,19,23,27,31,35,39,43,47,51,55,59,63 + /// 2150 DATA 4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64 + /// 2160 DATA 1,6,11,16,17,22,27,32,33,38,43,48,49,54,59,64 + /// 2170 DATA 13,10,7,4,29,26,23,20,45,42,39,36,61,58,55,52 + /// 2180 DATA 1,21,41,61,2,22,42,62,3,23,43,63,4,24,44,64 + /// 2190 DATA 49,37,25,13,50,38,26,14,51,39,27,15,52,40,28,16 + /// 2200 DATA 1,18,35,52,5,22,39,56,9,26,43,60,13,30,47,64 + /// 2210 DATA 49,34,19,4,53,38,23,8,57,42,27,12,61,46,31,16 + /// 2220 DATA 1,22,43,64,16,27,38,49,4,23,42,61,13,26,39,52 + /// + /// In short, each number is an index into the board. The data in this class + /// is zero-indexed, as opposed to the original data which was one-indexed. + /// + internal static class QubicData + { + /// + /// The corners and centers of the Qubic board. They correspond to the + /// following coordinates: + /// + /// [ + /// 111, 411, 414, 114, 141, 441, 444, 144, + /// 222, 323, 223, 322, 232, 332, 233, 333 + /// ] + /// + public static readonly int[] CornersAndCenters = new int[16] + { + // (X) ( ) ( ) (X) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // (X) ( ) ( ) (X) + + // ( ) ( ) ( ) ( ) + // ( ) (X) (X) ( ) + // ( ) (X) (X) ( ) + // ( ) ( ) ( ) ( ) + + // ( ) ( ) ( ) ( ) + // ( ) (X) (X) ( ) + // ( ) (X) (X) ( ) + // ( ) ( ) ( ) ( ) + + // (X) ( ) ( ) (X) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // (X) ( ) ( ) (X) + + 0,48,51,3,12,60,63,15,21,38,22,37,25,41,26,42 + }; + + /// + /// A list of all "winning" rows in the Qubic board; that is, sets of + /// four spaces that, if filled entirely by the player (or machine), + /// would result in a win. + /// + /// Each group of four rows in the list corresponds to a plane in the + /// cube, and each plane is organized so that the first and last rows + /// are on the plane's edges, while the second and third rows are in + /// the middle of the plane. The only exception is the last group of + /// rows, which contains the corners and centers rather than a plane. + /// + /// The order of the rows in this list is key to how the Qubic AI + /// decides its next move. + /// + public static readonly int[,] RowsByPlane = new int[76, 4] + { + // (1) (1) (1) (1) + // (2) (2) (2) (2) + // (3) (3) (3) (3) + // (4) (4) (4) (4) + + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + + { 0, 1, 2, 3, }, + { 4, 5, 6, 7, }, + { 8, 9, 10,11, }, + { 12,13,14,15, }, + + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + + // (1) (1) (1) (1) + // (2) (2) (2) (2) + // (3) (3) (3) (3) + // (4) (4) (4) (4) + + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + + { 16,17,18,19, }, + { 20,21,22,23, }, + { 24,25,26,27, }, + { 28,29,30,31, }, + + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + + // (1) (1) (1) (1) + // (2) (2) (2) (2) + // (3) (3) (3) (3) + // (4) (4) (4) (4) + + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + + { 32,33,34,35, }, + { 36,37,38,39, }, + { 40,41,42,43, }, + { 44,45,46,47, }, + + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + + // (1) (1) (1) (1) + // (2) (2) (2) (2) + // (3) (3) (3) (3) + // (4) (4) (4) (4) + + { 48,49,50,51, }, + { 52,53,54,55, }, + { 56,57,58,59, }, + { 60,61,62,63, }, + + // (1) ( ) ( ) ( ) + // (2) ( ) ( ) ( ) + // (3) ( ) ( ) ( ) + // (4) ( ) ( ) ( ) + + // (1) ( ) ( ) ( ) + // (2) ( ) ( ) ( ) + // (3) ( ) ( ) ( ) + // (4) ( ) ( ) ( ) + + // (1) ( ) ( ) ( ) + // (2) ( ) ( ) ( ) + // (3) ( ) ( ) ( ) + // (4) ( ) ( ) ( ) + + // (1) ( ) ( ) ( ) + // (2) ( ) ( ) ( ) + // (3) ( ) ( ) ( ) + // (4) ( ) ( ) ( ) + + { 0, 16,32,48, }, + { 4, 20,36,52, }, + { 8, 24,40,56, }, + { 12,28,44,60, }, + + // ( ) (1) ( ) ( ) + // ( ) (2) ( ) ( ) + // ( ) (3) ( ) ( ) + // ( ) (4) ( ) ( ) + + // ( ) (1) ( ) ( ) + // ( ) (2) ( ) ( ) + // ( ) (3) ( ) ( ) + // ( ) (4) ( ) ( ) + + // ( ) (1) ( ) ( ) + // ( ) (2) ( ) ( ) + // ( ) (3) ( ) ( ) + // ( ) (4) ( ) ( ) + + // ( ) (1) ( ) ( ) + // ( ) (2) ( ) ( ) + // ( ) (3) ( ) ( ) + // ( ) (4) ( ) ( ) + + { 1, 17,33,49, }, + { 5, 21,37,53, }, + { 9, 25,41,57, }, + { 13,29,45,61, }, + + // ( ) ( ) (1) ( ) + // ( ) ( ) (2) ( ) + // ( ) ( ) (3) ( ) + // ( ) ( ) (4) ( ) + + // ( ) ( ) (1) ( ) + // ( ) ( ) (2) ( ) + // ( ) ( ) (3) ( ) + // ( ) ( ) (4) ( ) + + // ( ) ( ) (1) ( ) + // ( ) ( ) (2) ( ) + // ( ) ( ) (3) ( ) + // ( ) ( ) (4) ( ) + + // ( ) ( ) (1) ( ) + // ( ) ( ) (2) ( ) + // ( ) ( ) (3) ( ) + // ( ) ( ) (4) ( ) + + { 2, 18,34,50, }, + { 6, 22,38,54, }, + { 10,26,42,58, }, + { 14,30,46,62, }, + + // ( ) ( ) ( ) (1) + // ( ) ( ) ( ) (2) + // ( ) ( ) ( ) (3) + // ( ) ( ) ( ) (4) + + // ( ) ( ) ( ) (1) + // ( ) ( ) ( ) (2) + // ( ) ( ) ( ) (3) + // ( ) ( ) ( ) (4) + + // ( ) ( ) ( ) (1) + // ( ) ( ) ( ) (2) + // ( ) ( ) ( ) (3) + // ( ) ( ) ( ) (4) + + // ( ) ( ) ( ) (1) + // ( ) ( ) ( ) (2) + // ( ) ( ) ( ) (3) + // ( ) ( ) ( ) (4) + + { 3, 19,35,51, }, + { 7, 23,39,55, }, + { 11,27,43,59, }, + { 15,31,47,63, }, + + // (1) ( ) ( ) ( ) + // (1) ( ) ( ) ( ) + // (1) ( ) ( ) ( ) + // (1) ( ) ( ) ( ) + + // (2) ( ) ( ) ( ) + // (2) ( ) ( ) ( ) + // (2) ( ) ( ) ( ) + // (2) ( ) ( ) ( ) + + // (3) ( ) ( ) ( ) + // (3) ( ) ( ) ( ) + // (3) ( ) ( ) ( ) + // (3) ( ) ( ) ( ) + + // (4) ( ) ( ) ( ) + // (4) ( ) ( ) ( ) + // (4) ( ) ( ) ( ) + // (4) ( ) ( ) ( ) + + { 0, 4, 8, 12, }, + { 16,20,24,28, }, + { 32,36,40,44, }, + { 48,52,56,60, }, + + // ( ) (1) ( ) ( ) + // ( ) (1) ( ) ( ) + // ( ) (1) ( ) ( ) + // ( ) (1) ( ) ( ) + + // ( ) (2) ( ) ( ) + // ( ) (2) ( ) ( ) + // ( ) (2) ( ) ( ) + // ( ) (2) ( ) ( ) + + // ( ) (3) ( ) ( ) + // ( ) (3) ( ) ( ) + // ( ) (3) ( ) ( ) + // ( ) (3) ( ) ( ) + + // ( ) (4) ( ) ( ) + // ( ) (4) ( ) ( ) + // ( ) (4) ( ) ( ) + // ( ) (4) ( ) ( ) + + { 1, 5, 9, 13, }, + { 17,21,25,29, }, + { 33,37,41,45, }, + { 49,53,57,61, }, + + // ( ) ( ) (1) ( ) + // ( ) ( ) (1) ( ) + // ( ) ( ) (1) ( ) + // ( ) ( ) (1) ( ) + + // ( ) ( ) (2) ( ) + // ( ) ( ) (2) ( ) + // ( ) ( ) (2) ( ) + // ( ) ( ) (2) ( ) + + // ( ) ( ) (3) ( ) + // ( ) ( ) (3) ( ) + // ( ) ( ) (3) ( ) + // ( ) ( ) (3) ( ) + + // ( ) ( ) (4) ( ) + // ( ) ( ) (4) ( ) + // ( ) ( ) (4) ( ) + // ( ) ( ) (4) ( ) + + { 2, 6, 10,14, }, + { 18,22,26,30, }, + { 34,38,42,46, }, + { 50,54,58,62, }, + + // ( ) ( ) ( ) (1) + // ( ) ( ) ( ) (1) + // ( ) ( ) ( ) (1) + // ( ) ( ) ( ) (1) + + // ( ) ( ) ( ) (2) + // ( ) ( ) ( ) (2) + // ( ) ( ) ( ) (2) + // ( ) ( ) ( ) (2) + + // ( ) ( ) ( ) (3) + // ( ) ( ) ( ) (3) + // ( ) ( ) ( ) (3) + // ( ) ( ) ( ) (3) + + // ( ) ( ) ( ) (4) + // ( ) ( ) ( ) (4) + // ( ) ( ) ( ) (4) + // ( ) ( ) ( ) (4) + + { 3, 7, 11,15, }, + { 19,23,27,31, }, + { 35,39,43,47, }, + { 51,55,59,63, }, + + // (1) ( ) ( ) ( ) + // ( ) (1) ( ) ( ) + // ( ) ( ) (1) ( ) + // ( ) ( ) ( ) (1) + + // (2) ( ) ( ) ( ) + // ( ) (2) ( ) ( ) + // ( ) ( ) (2) ( ) + // ( ) ( ) ( ) (2) + + // (3) ( ) ( ) ( ) + // ( ) (3) ( ) ( ) + // ( ) ( ) (3) ( ) + // ( ) ( ) ( ) (3) + + // (4) ( ) ( ) ( ) + // ( ) (4) ( ) ( ) + // ( ) ( ) (4) ( ) + // ( ) ( ) ( ) (4) + + { 0, 5, 10,15, }, + { 16,21,26,31, }, + { 32,37,42,47, }, + { 48,53,58,63, }, + + // ( ) ( ) ( ) (1) + // ( ) ( ) (1) ( ) + // ( ) (1) ( ) ( ) + // (1) ( ) ( ) ( ) + + // ( ) ( ) ( ) (2) + // ( ) ( ) (2) ( ) + // ( ) (2) ( ) ( ) + // (2) ( ) ( ) ( ) + + // ( ) ( ) ( ) (3) + // ( ) ( ) (3) ( ) + // ( ) (3) ( ) ( ) + // (3) ( ) ( ) ( ) + + // ( ) ( ) ( ) (4) + // ( ) ( ) (4) ( ) + // ( ) (4) ( ) ( ) + // (4) ( ) ( ) ( ) + + { 12,9, 6, 3, }, + { 28,25,22,19, }, + { 44,41,38,35, }, + { 60,57,54,51, }, + + // (1) (2) (3) (4) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + + // ( ) ( ) ( ) ( ) + // (1) (2) (3) (4) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // (1) (2) (3) (4) + // ( ) ( ) ( ) ( ) + + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // (1) (2) (3) (4) + + { 0, 20,40,60, }, + { 1, 21,41,61, }, + { 2, 22,42,62, }, + { 3, 23,43,63, }, + + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // (1) (2) (3) (4) + + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // (1) (2) (3) (4) + // ( ) ( ) ( ) ( ) + + // ( ) ( ) ( ) ( ) + // (1) (2) (3) (4) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + + // (1) (2) (3) (4) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + + { 48,36,24,12, }, + { 49,37,25,13, }, + { 50,38,26,14, }, + { 51,39,27,15, }, + + // (1) ( ) ( ) ( ) + // (2) ( ) ( ) ( ) + // (3) ( ) ( ) ( ) + // (4) ( ) ( ) ( ) + + // ( ) (1) ( ) ( ) + // ( ) (2) ( ) ( ) + // ( ) (3) ( ) ( ) + // ( ) (4) ( ) ( ) + + // ( ) ( ) (1) ( ) + // ( ) ( ) (2) ( ) + // ( ) ( ) (3) ( ) + // ( ) ( ) (4) ( ) + + // ( ) ( ) ( ) (1) + // ( ) ( ) ( ) (2) + // ( ) ( ) ( ) (3) + // ( ) ( ) ( ) (4) + + { 0, 17,34,51, }, + { 4, 21,38,55, }, + { 8, 25,42,59, }, + { 12,29,46,63, }, + + // ( ) ( ) ( ) (1) + // ( ) ( ) ( ) (2) + // ( ) ( ) ( ) (3) + // ( ) ( ) ( ) (4) + + // ( ) ( ) (1) ( ) + // ( ) ( ) (2) ( ) + // ( ) ( ) (3) ( ) + // ( ) ( ) (4) ( ) + + // ( ) (1) ( ) ( ) + // ( ) (2) ( ) ( ) + // ( ) (3) ( ) ( ) + // ( ) (4) ( ) ( ) + + // (1) ( ) ( ) ( ) + // (2) ( ) ( ) ( ) + // (3) ( ) ( ) ( ) + // (4) ( ) ( ) ( ) + + { 48,33,18,3, }, + { 52,37,22,7, }, + { 56,41,26,11, }, + { 60,45,30,15, }, + + // (1) ( ) ( ) (3) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // (4) ( ) ( ) (2) + + // ( ) ( ) ( ) ( ) + // ( ) (1) (3) ( ) + // ( ) (4) (2) ( ) + // ( ) ( ) ( ) ( ) + + // ( ) ( ) ( ) ( ) + // ( ) (2) (4) ( ) + // ( ) (3) (1) ( ) + // ( ) ( ) ( ) ( ) + + // (2) ( ) ( ) (4) + // ( ) ( ) ( ) ( ) + // ( ) ( ) ( ) ( ) + // (3) ( ) ( ) (1) + + { 0, 21,42,63, }, + { 15,26,37,48, }, + { 3, 22,41,60, }, + { 12,25,38,51, }, + }; + } +} From c85a1ba06eb5b0cca481840841bf06e8416978bd Mon Sep 17 00:00:00 2001 From: Noah Pauls Date: Mon, 17 Jan 2022 19:47:44 -0800 Subject: [PATCH 03/12] updated comments with better details --- 88_3-D_Tic-Tac-Toe/csharp/Qubic.cs | 104 ++++++++++++++++------------- 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/88_3-D_Tic-Tac-Toe/csharp/Qubic.cs b/88_3-D_Tic-Tac-Toe/csharp/Qubic.cs index 595db661..513651d6 100644 --- a/88_3-D_Tic-Tac-Toe/csharp/Qubic.cs +++ b/88_3-D_Tic-Tac-Toe/csharp/Qubic.cs @@ -568,9 +568,15 @@ namespace ThreeDTicTacToe /// /// If a player trap is not possible, but a row is found that is /// particularly advantageous for the machine to move to, the machine - /// will try and move to a corner-edge in that row. + /// will try and move to a plane edge in that row. /// /// Original BASIC: 1300-1480 + /// + /// Lines 1440/50 of the BASIC call 2360 (MovePlaneEdge). Because it + /// goes to this code only after it has found an open space marked as + /// potential, it cannot reach line 2440 of that code, as that is only + /// reached if an open space failed to be found in the row on which + /// that code was called. /// /// /// Move if a trap was created, @@ -618,11 +624,11 @@ namespace ThreeDTicTacToe // A row may be particularly advantageous for the machine to // move to at this point; this is the case if a row is entirely // filled with POTENTIAL or has one MACHINE and others - // POTENTIAL. + // POTENTIAL. Such rows may help set up trapping opportunities. if (RowSums[row] == (POTENTIAL * 4) || RowSums[row] == MACHINE + (POTENTIAL * 3)) { - // Try moving to a corner-edge in an advantageous row. - return MoveCornerEdges(row, POTENTIAL); + // Try moving to a plane edge in an advantageous row. + return MovePlaneEdge(row, POTENTIAL); } } @@ -637,9 +643,15 @@ namespace ThreeDTicTacToe /// /// If there are no player traps to block, but a row is found that is /// particularly advantageous for the player to move to, the machine - /// will try and move to a corner-edge in that row. + /// will try and move to a plane edge in that row. /// /// Original BASIC: 1030-1190 + /// + /// Lines 1160/1170 of the BASIC call 2360 (MovePlaneEdge). As with + /// MakePlayerTrap, because it goes to this code only after it has + /// found an open space marked as potential, it cannot reach line 2440 + /// of that code, as that is only reached if an open space failed to be + /// found in the row on which that code was called. /// /// /// Move if a trap was created, @@ -685,11 +697,12 @@ namespace ThreeDTicTacToe { // A row may be particularly advantageous for the player to move // to at this point, indicated by a row containing all POTENTIAL - // moves or one PLAYER and rest POTENTIAL. + // moves or one PLAYER and rest POTENTIAL. Such rows may aid in + // in the later creation of traps. if (RowSums[row] == (POTENTIAL * 4) || RowSums[row] == PLAYER + (POTENTIAL * 3)) { - // Try moving to a corner-edge in an advantageous row. - return MoveCornerEdges(row, POTENTIAL); + // Try moving to a plane edge in an advantageous row. + return MovePlaneEdge(row, POTENTIAL); } } @@ -743,24 +756,20 @@ namespace ThreeDTicTacToe /// /// Find a satisfactory plane on the board and move to one if that - /// plane's corner-edges. + /// plane's plane edges. /// /// A plane on the board is satisfactory if it meets the following /// conditions: /// 1. Player has made exactly 4 moves on the plane. /// 2. Machine has made either 0 or one moves on the plane. - /// The machine then attempts to move to a corner-edge in that plane, - /// first finding any potential moves from the previous action it took - /// and moving there, and moving to any open corner-edge if there are - /// no potential moves found. - /// - /// This action by the machine tries to prevent the player from having - /// exclusive control over any plane in the board. + /// Such a plane is one that the player could likely use to form traps. /// /// Original BASIC: 1830-2020 /// - /// The BASIC code for this action is tightly bound with base code for - /// MoveCornerEdges. + /// Line 1990 of the original basic calls 2370 (MovePlaneEdge). Only on + /// this call to MovePlaneEdge can line 2440 of that method be reached, + /// which surves to help this method iterate through the rows of a + /// plane. /// /// /// Move if a move in a plane was found, @@ -781,21 +790,21 @@ namespace ThreeDTicTacToe (planeSum >= P4_M1 && planeSum < P4_M1 + 1) ) { - // Try to move to corner edges in each row of plane - // First, check for corner edges marked as POTENTIAL. + // Try to move to plane edges in each row of plane + // First, check for plane edges marked as POTENTIAL. for (int row = (4 * plane) - 4; row < (4 * plane); row++) { - var moveResult = MoveCornerEdges(row, POTENTIAL); + var moveResult = MovePlaneEdge(row, POTENTIAL); if (moveResult != MachineAction.None) { return moveResult; } } - // If no POTENTIAL corner-edge found, look for an EMPTY one. + // If no POTENTIAL plane edge found, look for an EMPTY one. for (int row = (4 * plane) - 4; row < (4 * plane); row++) { - var moveResult = MoveCornerEdges(row, EMPTY); + var moveResult = MovePlaneEdge(row, EMPTY); if (moveResult != MachineAction.None) { return moveResult; @@ -804,48 +813,53 @@ namespace ThreeDTicTacToe } } - // No good corner edges found by plane. + // No satisfactory planes with open plane edges found. ClearPotentialMoves(); return MachineAction.None; } /// - /// Given a row, move to the first corner-edge of the cube that has the - /// given value. + /// Given a row, move to the first space in that row that: + /// 1. is a plane edge, and + /// 2. has the given value in Board /// - /// Corner edges are pieces of the cube that have two faces. The AI + /// Plane edges are any spaces on a plane with one face exposed. The AI /// prefers to move to these spaces before others, presumably - /// because they are powerful moves: a corner edge space is contained - /// in 3 rows on the cube. + /// because they are powerful moves: a plane edge is contained on 3-4 + /// winning rows of the cube. /// /// Original BASIC: 2360-2490 /// /// In the original BASIC, this code is pointed to from three different - /// locations by GOTOs (1440/50, or MakePlayerTrap; 1160/70, or - /// BlockMachineTrap; and 1990, or MoveByPlane). Interestingly, line - /// 2440 can only be reached if the code proceeds after a call from - /// 1990. In short, this means that the code flow is incredibly - /// difficult to understand; the block of code at 2360 acts like a - /// generalized subroutine, but contains bits of code that belong - /// to specific pieces of calling code. + /// locations by GOTOs: + /// - 1440/50, or MakePlayerTrap; + /// - 1160/70, or BlockMachineTrap; and + /// - 1990, or MoveByPlane. + /// At line 2440, this code jumps back to line 2000, which is in + /// MoveByPlane. This makes it appear as though calling MakePlayerTrap + /// or BlockPlayerTrap in the BASIC could jump into the middle of the + /// MoveByPlane method; were this to happen, not all of MoveByPlane's + /// variables would be defined! However, the program logic prevents + /// this from ever occurring; see each method's description for why + /// this is the case. /// /// the row to try to move to /// /// what value the space to move to should have in Board /// /// - /// Move if a corner-edge piece in the row with the given spaceValue was + /// Move if a plane edge piece in the row with the given spaceValue was /// found, /// None otherwise /// - private MachineAction MoveCornerEdges(int row, double spaceValue) + private MachineAction MovePlaneEdge(int row, double spaceValue) { - // Given a row, we want to find the corner-edge pieces in that row. + // Given a row, we want to find the plane edge pieces in that row. // We know that each row is part of a plane, and that the first // and last rows of the plane are on the plane edge, while the // other two rows are in the middle. If we know whether a row is an // edge or middle, we can determine which spaces in that row are - // corner-edges. + // plane edges. // // Below is a birds-eye view of a plane in the cube, with rows // oriented horizontally: @@ -855,11 +869,11 @@ namespace ThreeDTicTacToe // row 2: (0) ( ) ( ) (3) // row 3: ( ) (1) (2) ( ) // - // The corner edge pieces have their row indices marked. The pattern + // The plane edge pieces have their row indices marked. The pattern // above shows that: // - // if row == 0 | 3, corner edge spaces = [1, 2] - // if row == 1 | 2, corner edge spaces = [0, 3] + // if row == 0 | 3, plane edge spaces = [1, 2] + // if row == 1 | 2, plane edge spaces = [0, 3] // The below condition replaces the following BASIC code (2370): // @@ -883,7 +897,7 @@ namespace ThreeDTicTacToe _ => throw new Exception($"unreachable ({row % 4})"), }; - // Iterate through corner edge pieces of the row. + // Iterate through plane edge pieces of the row. // // if a = 1 (row is edge), iterate through [0, 3] // if a = 2 (row is middle), iterate through [1, 2] @@ -891,7 +905,7 @@ namespace ThreeDTicTacToe { if (Board[RowsByPlane[row, space]] == spaceValue) { - // Found a corner-edge to take! + // Found a plane edge to take! Board[RowsByPlane[row, space]] = MACHINE; Console.WriteLine($"MACHINE TAKES {IndexToCoord(RowsByPlane[row, space])}"); return MachineAction.Move; From 1aa0fa1a244bec481f4b9dd84906e538ae7c355c Mon Sep 17 00:00:00 2001 From: openback Date: Tue, 18 Jan 2022 00:26:34 -0500 Subject: [PATCH 04/12] Add Battle in python --- 09_Battle/python/battle.py | 165 +++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 09_Battle/python/battle.py diff --git a/09_Battle/python/battle.py b/09_Battle/python/battle.py new file mode 100644 index 00000000..8c992458 --- /dev/null +++ b/09_Battle/python/battle.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +from random import randrange +from typing import List, Tuple + +PointType = Tuple[int, int] +VectorType = PointType +SeaType = Tuple[List[int], ...] + +SEA_WIDTH = 6 +DESTROYER_LENGTH = 2 +CRUISER_LENGTH = 3 +AIRCRAFT_CARRIER_LENGTH = 4 + + +def random_vector() -> Tuple[int, int]: + while True: + vector = (randrange(-1, 2), randrange(-1, 2)) + + if vector == (0, 0): + # We can't have a zero vector, so try again + continue + + return vector + + +def add_vector(point: PointType, vector: VectorType) -> PointType: + return (point[0] + vector[0], point[1] + vector[1]) + + +def place_ship(sea: SeaType, size: int, code: int) -> None: + while True: + start = (randrange(1, SEA_WIDTH + 1), randrange(1, SEA_WIDTH + 1)) + vector = random_vector() + + # Get potential ship points + point = start + points = [] + + for _ in range(size): + point = add_vector(point, vector) + points.append(point) + + if not (all([is_within_sea(point, sea) for point in points]) and + all([value_at(point, sea) == 0 for point in points])): + # ship out of bounds or crosses other ship, trying again + continue + + # We found a valid spot, so actually place it now + for point in points: + set_value_at(code, point, sea) + + break + + +def print_encoded_sea(sea: SeaType) -> None: + for x in range(len(sea)): + print(' '.join([str(sea[y][x]) for y in range(len(sea) - 1, -1, -1)])) + + +def is_within_sea(point: PointType, sea: SeaType) -> bool: + return (1 <= point[0] <= len(sea)) and (1 <= point[1] <= len(sea)) + + +def has_ship(sea: SeaType, code: int) -> bool: + return any(code in row for row in sea) + + +def count_sunk(sea: SeaType, codes: Tuple[int, ...]) -> int: + return sum(not has_ship(sea, code) for code in codes) + + +def value_at(point: PointType, sea: SeaType) -> int: + return sea[point[1] - 1][point[0] -1] + + +def set_value_at(value: int, point: PointType, sea: SeaType) -> None: + sea[point[1] - 1][point[0] -1] = value + + +def get_next_target(sea: SeaType) -> PointType: + while True: + try: + guess = input('? ') + point = guess.split(',') + + if len(point) != 2: + raise ValueError() + + point = (int(point[0]), int(point[1])) + + if not is_within_sea(point, sea): + raise ValueError() + + return point + except ValueError: + print(f'INVALID. SPECIFY TWO NUMBERS FROM 1 TO {len(sea)}, SEPARATED BY A COMMA.') + + +def setup_ships(sea: SeaType): + place_ship(sea, DESTROYER_LENGTH, 1) + place_ship(sea, DESTROYER_LENGTH, 2) + place_ship(sea, CRUISER_LENGTH, 3) + place_ship(sea, CRUISER_LENGTH, 4) + place_ship(sea, AIRCRAFT_CARRIER_LENGTH, 5) + place_ship(sea, AIRCRAFT_CARRIER_LENGTH, 6) + + +def main() -> None: + print(' BATTLE') + print('CREATIVE COMPUTING MORRISTOWN, NEW JERSEY') + print() + sea = tuple(([0 for _ in range(SEA_WIDTH)] for _ in range(SEA_WIDTH))) + setup_ships(sea) + print('THE FOLLOWING CODE OF THE BAD GUYS\' FLEET DISPOSITION') + print('HAS BEEN CAPTURED BUT NOT DECODED:') + print() + print_encoded_sea(sea) + print() + print('DE-CODE IT AND USE IT IF YOU CAN') + print('BUT KEEP THE DE-CODING METHOD A SECRET.') + print() + print('START GAME') + splashes = 0 + hits = 0 + + while True: + target = get_next_target(sea) + target_value = value_at(target, sea) + + if target_value < 0: + print(f'YOU ALREADY PUT A HOLE IN SHIP NUMBER {abs(target_value)} AT THAT POINT.') + + if target_value <= 0: + print('SPLASH! TRY AGAIN.') + splashes += 1 + continue + + print(f'A DIRECT HIT ON SHIP NUMBER {target_value}') + hits += 1 + set_value_at(-target_value, target, sea) + + if not has_ship(sea, target_value): + print('AND YOU SUNK IT. HURRAH FOR THE GOOD GUYS.') + print('SO FAR, THE BAD GUYS HAVE LOST') + print(f'{count_sunk(sea, (1, 2))} DESTROYER(S),', + f'{count_sunk(sea, (3, 4))} CRUISER(S),', + f'AND {count_sunk(sea, (5, 6))} AIRCRAFT CARRIER(S).') + + if any(has_ship(sea, code) for code in range(1, SEA_WIDTH + 1)): + print(f'YOUR CURRENT SPLASH/HIT RATIO IS {splashes}/{hits}') + continue + + print('YOU HAVE TOTALLY WIPED OUT THE BAD GUYS\' FLEET ' + f'WITH A FINAL SPLASH/HIT RATIO OF {splashes}/{hits}') + + if not splashes: + print('CONGRATULATIONS -- A DIRECT HIT EVERY TIME.') + + print() + print('****************************') + break + + +if __name__ == "__main__": + main() From d5a032e6423b9e92579d955b0567538deca9791a Mon Sep 17 00:00:00 2001 From: Nezumi Ronin Date: Tue, 18 Jan 2022 10:56:57 -0600 Subject: [PATCH 05/12] Create life.pl --- 55_Life/perl/life.pl | 95 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 55_Life/perl/life.pl diff --git a/55_Life/perl/life.pl b/55_Life/perl/life.pl new file mode 100644 index 00000000..972cb322 --- /dev/null +++ b/55_Life/perl/life.pl @@ -0,0 +1,95 @@ +#!/usr/bin/perl +#use strict; + +print ' 'x 34 . "LIFE\n"; +print ' 'x 15 . "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"; +print "\n"; print "\n"; print "\n"; +print "ENTER YOUR PATTERN; \n"; +$X1=1; $Y1=1; $X2=24; $Y2=70; +@A; +$C=1; + +@B; +Line30: +print "? "; chomp($B[$C] = uc()); +if ($B[$C] eq "DONE") { $B[$C]=""; goto Line80; } +$B[$C]=~ s/\./ /g; +$C=$C+1; +goto Line30; + + +Line80: + +$C=$C-1; $L=0; $G=0; +for ($X=1; $X<=$C-1; $X++) { + if (length($B[$X])>$L) { $L=length($B[$X]); } + } + +$X1=11-$C/2; +$Y1=33-$L/2; +for ($X=1; $X<=$C; $X++) { + for ($Y=1; $Y<=length($B[$X]); $Y++) { + if (substr($B[$X],$Y-1,1) ne " ") { $A[$X1+$X][$Y1+$Y]=1; $P=$P+1; } + } + } +print "\n"; print "\n"; print "\n"; + +Line210: +print "GENERATION: ".$G."\t\tPOPULATION: ".$P; if ($I9) { print "\tINVALID!"; } +print "\n"; +$X3=24; $Y3=70; $X4=1; $Y4=1; $P=0; +$G=$G+1; +for ($X=1; $X<=$X1-1; $X++) { print "\n"; } +for ($X=$X1; $X<=$X2; $X++) { + $Row= " "x 80; + for ($Y=$Y1; $Y<=$Y2; $Y++) { + if ($A[$X][$Y]==2) { $A[$X][$Y]=0; goto Line270; } + if ($A[$X][$Y]==3) { $A[$X][$Y]=1; goto Line261; } + if ($A[$X][$Y]!=1) { goto Line270; } + + Line261: + substr($Row, $Y, 1, "*"); + if ($X<$X3) { $X3=$X; } + if ($X>$X4) { $X4=$X; } + if ($Y<$Y3) { $Y3=$Y; } + if ($Y>$Y4) { $Y4=$Y; } + + Line270: + } + print "$Row\n"; + } + +for ($X=$X2+1; $X<=24; $X++) { print "\n"; } +$X1=$X3; $X2=$X4; $Y1=$Y3; $Y2=$Y4; +if ($X1<3) { $X1=3; $I9=-1; } +if ($X2>22) { $X2=22; $I9=-1; } +if ($Y1<3) { $Y1=3; $I9=-1; } +if ($Y2>68) { $Y2=68; $I9=-1; } +$P=0; + +for ($X=$X1-1; $X<=$X2+1; $X++) { + for ($Y=$Y1-1; $Y<=$Y2+1; $Y++) { + $C=0; + for ($I=$X-1; $I<=$X+1; $I++) { + for ($J=$Y-1; $J<=$Y+1; $J++) { + if ($A[$I][$J]==1 || $A[$I][$J]==2) { $C=$C+1; } + } + } + if ($A[$X][$Y]==0) { goto Line610; } + if ($C<3 || $C>4) { $A[$X][$Y]=2; goto Line600; } + $P=$P+1; + + Line600: + goto Line620; + + Line610: + if ($C==3) { $A[$X][$Y]=3; $P=$P+1; } + + Line620: + } + } +$X1=$X1-1; $Y1=$Y1-1; $X2=$X2+1; $Y2=$Y2+1; +goto Line210; +exit; + + From 7a2929f55d4228d1496a30289069edcb1325dbfe Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Wed, 19 Jan 2022 18:41:24 -0800 Subject: [PATCH 06/12] Update README.md add notes about difficulty of porting 3d tic tac toe due to extensive use of `GOTO` for complex logic. --- 88_3-D_Tic-Tac-Toe/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/88_3-D_Tic-Tac-Toe/README.md b/88_3-D_Tic-Tac-Toe/README.md index df9cabf4..678bac6b 100644 --- a/88_3-D_Tic-Tac-Toe/README.md +++ b/88_3-D_Tic-Tac-Toe/README.md @@ -6,6 +6,15 @@ Each move is indicated by a 3-digit number (digits not separated by commas), wit This version of 3-D TIC-TAC-TOE is from Dartmouth College. +### Conversion notes + +The AI code for TicTacToe2 depends quite heavily on the non-structured GOTO (I can almost hear Dijkstra now) and translation is quite challenging. This code relies very heavily on GOTOs that bind the code tightly together. Comments explain where that happens in the original. + +There are at least two bugs from the original BASIC: + +1. Code should only allow player to input valid 3D coordinates where every digit is between 1 and 4, but the original code allows any value between 111 and 444 (such as 297, for instance). +2. If the player moves first and the game ends in a draw, the original program will still prompt the player for a move instead of calling for a draw. + --- As published in Basic Computer Games (1978): From ce771b6d3c5e236e69ca3c228a6e6395601fa1a0 Mon Sep 17 00:00:00 2001 From: Claus Volko <49327712+adokhugi@users.noreply.github.com> Date: Thu, 20 Jan 2022 10:38:04 +0100 Subject: [PATCH 07/12] Create aceyducey.kt --- 01_Acey_Ducey/kotlin/aceyducey.kt | 74 +++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 01_Acey_Ducey/kotlin/aceyducey.kt diff --git a/01_Acey_Ducey/kotlin/aceyducey.kt b/01_Acey_Ducey/kotlin/aceyducey.kt new file mode 100644 index 00000000..fe7c32c7 --- /dev/null +++ b/01_Acey_Ducey/kotlin/aceyducey.kt @@ -0,0 +1,74 @@ +import java.util.Random + +fun printCard(a: Int) { + if (a < 11) println(a) + if (a == 11) println("JACK") + if (a == 12) println("QUEEN") + if (a == 13) println("KING") + if (a == 14) println("ACE") +} + +fun main() { + println("ACEY DUCEY CARD GAME") + println("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + println() + println() + println("ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER ") + println("THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP") + println("YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING") + println("ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE") + println("A VALUE BETWEEN THE FIRST TWO.") + println("IF YOU DO NOT WANT TO BET, INPUT A 0") + var random = Random() + do { + var q = 100 + var a : Int + var b : Int + var m : Int + println("YOU NOW HAVE " + q + " DOLLARS.") + println() + do { + do { + do { + println("HERE ARE YOUR NEXT TWO CARDS: ") + do { + a = random.nextInt(12) + 2 + b = random.nextInt(12) + 2 + } while (a >= b); + printCard(a) + printCard(b) + println() + println() + print("WHAT IS YOUR BET") + m = readLine()!!.toInt() + if (m == 0) { + println("CHICKEN!!") + println() + } + } while (m == 0); + if (m > q) { + println("SORRY, MY FRIEND, BUT YOU BET TOO MUCH.") + println("YOU HAVE ONLY " + q + " DOLLARS TO BET.") + } + } while (m > q); + var c = random.nextInt(12) + 2 + printCard(c) + println() + if (c > a && c < b) { + println("YOU WIN!!!") + q += m + } + else { + println("SORRY, YOU LOSE") + if (m < q) q -= m + } + } while (m < q); + println() + println() + println("SORRY, FRIEND, BUT YOU BLEW YOUR WAD.") + println() + println() + println("TRY AGAIN (YES OR NO)") + } while (readLine() == "YES"); + println("O.K., HOPE YOU HAD FUN!") +} From 33af445d20d31188fb29b2eb8e8ef7b47aac968c Mon Sep 17 00:00:00 2001 From: Claus Volko <49327712+adokhugi@users.noreply.github.com> Date: Thu, 20 Jan 2022 10:38:20 +0100 Subject: [PATCH 08/12] Delete aceyducey.kt --- 01_Acey_Ducey/aceyducey.kt | 74 -------------------------------------- 1 file changed, 74 deletions(-) delete mode 100644 01_Acey_Ducey/aceyducey.kt diff --git a/01_Acey_Ducey/aceyducey.kt b/01_Acey_Ducey/aceyducey.kt deleted file mode 100644 index ccc5eb0a..00000000 --- a/01_Acey_Ducey/aceyducey.kt +++ /dev/null @@ -1,74 +0,0 @@ -import java.util.Random - -fun printCard(a: Int) { - if (a < 11) println(a) - if (a == 11) println("JACK") - if (a == 12) println("QUEEN") - if (a == 13) println("KING") - if (a == 14) println("ACE") -} - -fun main() { - println("ACEY DUCEY CARD GAME") - println("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") - println() - println() - println("ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER ") - println("THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP") - println("YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING") - println("ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE") - println("A VALUE BETWEEN THE FIRST TWO.") - println("IF YOU DO NOT WANT TO BET, INPUT A 0") - var random = Random() - do { - var q = 100 - var a : Int - var b : Int - var m : Int - println("YOU NOW HAVE " + q + " DOLLARS.") - println() - do { - do { - do { - println("HERE ARE YOUR NEXT TWO CARDS: ") - do { - a = random.nextInt(12) + 2 - b = random.nextInt(12) + 2 - } while (a >= b); - printCard(a) - printCard(b) - println() - println() - print("WHAT IS YOUR BET") - m = readLine()!!.toInt() - if (m == 0) { - println("CHICKEN!!") - println() - } - } while (m == 0); - if (m > q) { - println("SORRY, MY FRIEND, BUT YOU BET TOO MUCH.") - println("YOU HAVE ONLY " + q + " DOLLARS TO BET.") - } - } while (m > q); - var c = random.nextInt(12) + 2 - printCard(c) - println() - if (c > a && c < b) { - println("YOU WIN!!!") - q += m - } - else { - println("SORRY, YOU LOSE") - if (m < q) q -= m - } - } while (m < q); - println() - println() - println("SORRY, FRIEND, BUT YOU BLEW YOUR WAD.") - println() - println() - println("TRY AGAIN (YES OR NO)") - } while (readLine() == "YES"); - println("O.K., HOPE YOU HAD FUN!") -} From 37eb53b4f1e0d3963c8e1918a6f86273aba2f0f4 Mon Sep 17 00:00:00 2001 From: Nezumi Ronin Date: Thu, 20 Jan 2022 16:02:04 -0600 Subject: [PATCH 09/12] Create yatol.pl YATOL: Yet Another TOdo List Get list of basic files ordered by number or lines. This way you can do the easier ones first. --- 00_Utilities/yatol.pl | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 00_Utilities/yatol.pl diff --git a/00_Utilities/yatol.pl b/00_Utilities/yatol.pl new file mode 100644 index 00000000..71861209 --- /dev/null +++ b/00_Utilities/yatol.pl @@ -0,0 +1,35 @@ +#!/usr/bin/perl +#YATOL: Yet Another TOdo List +use strict; + +#REM: Get list of basic files ordered by number or lines. +#REM: This way you can do the easier ones first. +my @Ret=`find .. -iname '*.bas' -exec wc -l \{\} \\; | sort -h`; + + +my @Langs= qw(PL JS VB PAS RB C# JAVA PY); +my @Dirs= qw(perl javascript vbnet pascal ruby csharp java python); + +print " "x 25 ."LINES\t"; +foreach my $Dir (@Langs) { + print "$Dir\t"; + } +print "\n"; + +foreach my $Lin (@Ret) { + chomp $Lin; + my ($Num, $File)= split (" ", $Lin); + my @Parts= split(/\//, $File); + my $Base= $Parts[1]; + + my $Tab= 25-length($Base); + print "$Base".(" "x$Tab)."$Num\t"; + + foreach my $Dir (@Dirs) { + my $Path= "../$Base/$Dir/"; + my $Ret= `ls $Path | wc -l`; + if ($Ret>1) { print "YES"; } + else { print " ";} + print "\t"; + } + print "\n"; From a8c3604986a9a55df7aefc58567dcbcded820abd Mon Sep 17 00:00:00 2001 From: Nezumi Ronin Date: Thu, 20 Jan 2022 16:07:19 -0600 Subject: [PATCH 10/12] Update yatol.pl --- 00_Utilities/yatol.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/00_Utilities/yatol.pl b/00_Utilities/yatol.pl index 71861209..136c3d67 100644 --- a/00_Utilities/yatol.pl +++ b/00_Utilities/yatol.pl @@ -33,3 +33,4 @@ foreach my $Lin (@Ret) { print "\t"; } print "\n"; + } From 075e10c694b0d28dcd823f917a76213a66136441 Mon Sep 17 00:00:00 2001 From: Alex Gomez Date: Thu, 20 Jan 2022 18:05:52 -0600 Subject: [PATCH 11/12] Execute bit now --- 00_Utilities/yatol.pl | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 00_Utilities/yatol.pl diff --git a/00_Utilities/yatol.pl b/00_Utilities/yatol.pl old mode 100644 new mode 100755 From 9ca24407fe3d1d6a733031997dd826cbab6c008d Mon Sep 17 00:00:00 2001 From: Alex Gomez Date: Thu, 20 Jan 2022 18:10:04 -0600 Subject: [PATCH 12/12] Sorry, I'm learning git --- 00_Utilities/yatol.pl | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/00_Utilities/yatol.pl b/00_Utilities/yatol.pl index 136c3d67..0f4ef111 100755 --- a/00_Utilities/yatol.pl +++ b/00_Utilities/yatol.pl @@ -2,21 +2,24 @@ #YATOL: Yet Another TOdo List use strict; -#REM: Get list of basic files ordered by number or lines. +#REM: Get list of basic files ordered by number of lines. #REM: This way you can do the easier ones first. my @Ret=`find .. -iname '*.bas' -exec wc -l \{\} \\; | sort -h`; my @Langs= qw(PL JS VB PAS RB C# JAVA PY); my @Dirs= qw(perl javascript vbnet pascal ruby csharp java python); +my %Sum; -print " "x 25 ."LINES\t"; +print " "x 25 ."BAS\t"; foreach my $Dir (@Langs) { print "$Dir\t"; } print "\n"; +my $Count; foreach my $Lin (@Ret) { + $Count++; chomp $Lin; my ($Num, $File)= split (" ", $Lin); my @Parts= split(/\//, $File); @@ -28,9 +31,25 @@ foreach my $Lin (@Ret) { foreach my $Dir (@Dirs) { my $Path= "../$Base/$Dir/"; my $Ret= `ls $Path | wc -l`; - if ($Ret>1) { print "YES"; } + if ($Ret>1) { print "YES"; $Sum{$Dir}++; } else { print " ";} print "\t"; } print "\n"; + } + +print "\t\tFILES:\t\t"; +foreach my $Dir (@Dirs) { + print "$Sum{$Dir}\t"; + } +print "\n"; + + +print "\t\tADVANCE:\t"; +foreach my $Dir (@Dirs) { + my $Per= int($Sum{$Dir}/$Count*100)."%"; + print "$Per\t"; + } +print "\n"; +