So I have the game already programmed. You take turns against the computer picking 1-3 straws and the point of the game is to leave the opponent with 1 straw. That entire section works, but now I have to program the Computer to get smarter through consecutive play.
I'm not entirely sure how this is done, however. The way it was explained to me was that you have 4 "cups", which I assume are arrays. Each cup contains the moves, (1, 2, 3). During the computer's turn it randomly picks a cup and randomly chooses one of the three moves. If that move doesn't work then it removes it from the cup.
After several games the computer should become unbeatable.I'm going to keep working on this but I'm having a lot of trouble. I've been at it for a couple hours now so I'll just post the raw code without my broken parts.
EDIT: I've added the Cup class at the bottom.
import java.util.*;
public class Nim
{
public static void main(String[] args)
{
Random r = new Random();
Scanner kb = new Scanner(System.in);
String reply;
int straws, cupNum, prevCupNum, prevComputerMove,
computerMove, humanMove, gameNumber = 0, humanWin = 0;
// create an array of four cups
Cup[] cup = new Cup[4];
for (int i = 0; i < cup.length; i++)
cup[i] = new Cup();
System.out.println("Let's play Nim");
System.out.println();
do
{
gameNumber++;
straws = r.nextInt(11) + 10;
// add 1 if necessary so computer nevers starts in losing config
if (straws%4 == 1)
straws++;
System.out.println("Straws to start = " + straws);
System.out.println();
// set to -1 to indicate just starting so no previous move
prevCupNum = -1; // no prev move so init to -1
prevComputerMove = -1;
do
{
cupNum = straws%4; // get cup number
// if cup is empty, then use 1 for move
if (cup[cupNum].count() == 0) // cup empty then move = 1
computerMove = 1;
else
computerMove = cup[cupNum].select(); // get move from cup
/*MISSING CODE
If cup is cup number 1, then remove the previous move
from the previous cup unless already empty*/
System.out.println
("Computer picks up " + computerMove + " straws");
straws = straws - computerMove;
if (straws < 0)
straws = 0;
System.out.println(straws + " left");
System.out.println();
// save this move so it can be removed later if necessary
prevCupNum = cupNum;
prevComputerMove = computerMove;
if (straws == 0)
{
System.out.println("Wow. You win!");
humanWin++;
/* MISSING CODE
Remove last move computer made from cup*/
}
else // get move from human
{
System.out.println();
do
{
System.out.print("Your move: enter ");
if (straws == 1)
System.out.println("1");
else
if (straws == 2)
System.out.println("1 or 2");
else
if (straws >= 3)
System.out.println("1, 2, or 3");
humanMove = kb.nextInt();
} while
(humanMove > 3 || humanMove < 1 || straws - humanMove < 0);
straws = straws - humanMove;
System.out.println(straws + " left");
if (straws == 0)
{
System.out.println();
System.out.println("Ha, ha. You lose!");
}
}
} while (straws > 0);
System.out.println
("Score: Human " + humanWin + " Computer " +
(gameNumber - humanWin));
if (kb.hasNextLine()) // get rid of stray newline
kb.nextLine();
System.out.println();
System.out.println("Want to play another game? Hit enter.");
System.out.println
("Or are you too CHICKEN? In that case, type in quit.");
reply = kb.nextLine();
reply = reply.trim();
if (reply.length() > 4) // require only 1st 4 to be quit
reply = reply.substring(0, 4);
}
while (!reply.toLowerCase().equals("quit"));
}
public class Cup
{
ArrayList<Integer> c = new ArrayList<Integer>();
public Cup()
{
c.add(1);
c.add(2);
c.add(3);
}
//-----------------------
public int count()
{
return c.size();
}
//-----------------------
public int select()
{
// random is a static method in Math that returns a double
// value greater than or equal to 0.0 and less than 1.0.
int index = (int)(c.size()*Math.random());
return c.get(index);
}
//-----------------------
public void remove(Integer move)
{
c.remove(move);
}
}
I'm afraid I don't understand the 'cup' strategy. There is a much simpler strategy to force the machine to learn through play, however. As it plays, record the number of straws it leaves after its turn. If it loses then ensure that it doesn't leave that many straws in turns in subsequent games.
Your code would look something like:
class AI_Player {
private final Set<Integer> losingMoves = new HashSet<>();
private final Set<Integer> currentGameMoves = new HashSet<>();
public void startGame() {
currentGameMoves.clear();
}
public int nextMove(int strawsRemaining) {
List<Integer> possibleMoves = Stream.of(1, 2, 3)
.filter(move -> move < strawsRemaining)
.filter(move -> !losingMoves.contains(strawsRemaining - move))
.collect(Collectors.toList());
int move = possibleMoves.get(Random.nextInt(possibleMoves.size()));
currentGameMoves.add(strawsRemaining - move);
return move;
}
public void registerLoss() {
losingMoves.addAll(currentGameMoves);
}
public void registerWin() {
losingMoves.removeAll(currentGameMoves);
}
}
This uses Java 8 streams but it's fairly trivial to convert if you haven't used them yet.
Related
Where the commented section is, it says that there is a StackOverflowError - null. I am trying to get it to make random numbers to match up with an inputted value. The goal of this code is to do the following:
Accept a top number (ie 1000 in order to have a scale of (1-1000)).
Accept an input as the number for the computer to guess.
Computer randomly guesses the first number and checks to see if it is correct.
If it is not correct, it should go through a loop and randomly guess numbers, adding them to an ArrayList, until it guesses the input. It should check to see if the guess is already in the array and will generate another random number until it makes one that isn't in the list.
In the end, it will print out the amount of iterations with the count variable.
Code:
import java.util.*;
public class ArrNumGuess
{
public static Integer top, input, guess, count;
public static ArrayList <Integer> nums;
public static void main ()
{
System.out.println("Please enter the top number");
top = (new Scanner(System.in)).nextInt();
System.out.println("Please enter the number to guess (1 - " + top + ")");
input = Integer.parseInt(((new Scanner(System.in)).nextLine()).trim());
nums = new ArrayList<Integer>(); //use nums.contains(guess);
guess = (new Random()).nextInt(top) + 1;
nums.add(guess);
System.out.println("My first guess is " + guess);
count = 1;
if(guess != input)
{
guesser();
}
System.out.println("It took me " + count + " tries to find " + guess + " and " + input);
}
public static void guesser()
{
boolean check = false;
while(!check)
{
guess = (new Random()).nextInt(top) + 1; //Stack Overflow - null
if(nums.contains(guess) && !(guess.equals(input)))
{
count--;
guesser();
}
else if(guess.equals(input))
{
check = true;
System.out.println("My guess was " + guess);
// nums.add(guess);
count++;
}
else
{
System.out.println("My guess was " + guess);
nums.add(guess);
count++;
}
}
}
}
In guesser() method, you're invoking itself:
if(nums.contains(guess) && !(guess.equals(input)))
{
count--;
guesser();
}
There is quite a possibility it will never end. But all that is in while loop, so why not get rid of recurrence and do this in an iterative style?
OK - a different approach to your guesser for fun. Enumerate a randomized sequence of numbers in specified range (1 to 'top') and find the guess in the list whose index is effectively the number of "attempts" and return.
(BTW - #Andronicus answer is the correct one.)
/** Pass in 'guess' to find and 'top' limit of numbers and return number of guesses. */
public static int guesser(int guess, int top) {
List<Integer> myNums;
Collections.shuffle((myNums = IntStream.rangeClosed(1, top).boxed().collect(Collectors.toList())), new Random(System.currentTimeMillis()));
return myNums.indexOf(guess);
}
You are making it more complicated than it needs to be and introducing recursion unnecessarily. The recursion is the source of your stack overflow as it gets too deep before it "guesses" correctly.
There is a lot of sloppiness in there as well. Here's a cleaned up version:
import java.util.*;
public class Guess {
public static void main(String args[]) {
System.out.println("Please enter the top number");
Scanner scanner = new Scanner(System.in);
int top = scanner.nextInt();
System.out.println("Please enter the number to guess (1 - " + top + ")");
int input = scanner.nextInt();
if (input < 1 || input > top) {
System.out.println("That's not in range. Aborting.");
return;
}
ArrayList <Integer> nums = new ArrayList<>();
Random rng = new Random(System.currentTimeMillis());
while(true) {
int guess = rng.nextInt(top) + 1;
if (!nums.contains(guess)) {
nums.add(guess);
if (nums.size() == 1) {
System.out.println("My first guess is " + guess);
} else {
System.out.println("My guess was " + guess);
}
if (guess == input) {
System.out.println("It took me " + nums.size() + " tries to find " + guess);
break;
}
}
}
}
}
In my code I have a variable, points, that increments based on the consanants and vowels in strings inputted. The method parseSentence is supposed to increase points per word but also ignore spaces.
I've tried running a debugger to see where the problem is but the debugger dies when it reaches the for loop in parseSentence. The method makes the point variable's value the word's point value instead of adding it to the variable. What could be causing this?
import java.util.*;
public class WordGolf1 {
public static int points = 1;
public static void main(String[] args) {
String Input;
System.out.println("Enter word: ");
Scanner sc = new Scanner(System.in);
Input = sc.nextLine();
System.out.println("Not enough points. " + (100 - points) + " needed.");
while (points < 100) {
System.out.println("Enter word: ");
Input = sc.nextLine();
parseSentence(Input);
System.out.println(points + ": points");
System.out.println("Not enough points. " + (100 - points) + " needed.");
}
boolean overshot = true;
Loop:
while (overshot = true) {
if (points == 100) {
overshot = false;
break Loop;
}
points = 100 - (points - 100);
System.out.println("Overshot by " + (points - 100) + " points.");
Input = sc.nextLine();
parseSentence(Input);
}
System.out.println("Congratulations you win!");
sc.close();
}
public static int parseSentence(String input) {
String[] pieces = input.split("\\s+");
for (int y = 0; y < pieces.length; y++) {
if (pieces.length > 1) {
if (y == 0) {
parseWord(input);
} else {
parseWord(input, y);
}
} else {
parseWord(input);
}
}
return points;
}
public static int parseWord(String input) {
String[] pieces = input.split("\\s+");
String charList = "aeiouyAEIOUY";
String consanantList
= "bcdfghjklmnpqrstvwxzBCDFGHJKLMNPQRSTVWXZ";
int pointsTemp = 1;
for (int x = 0; x < pieces[0].length(); x++) {
if (charList.indexOf(pieces[0].charAt(x)) != -1) {
pointsTemp *= 2;
} else if (consanantList.indexOf(pieces[0].charAt(x))
!= -1) {
pointsTemp++;
}
}
points = pointsTemp;
return points;
}
public static int parseWord(String input, int number) {
String[] pieces = input.split("\\s+");
String charList = "aeiouyAEIOUY";
String consanantList
= "bcdfghjklmnpqrstvwxzBCDFGHJKLMNPQRSTVWXZ";
int pointsTemp = 1;
for (int x = 0; x < pieces[number].length(); x++) {
if (charList.indexOf(pieces[number].charAt(x)) != -1) {
pointsTemp *= 2;
} else if (consanantList.indexOf(pieces[number].charAt(x)) != -1) {
pointsTemp++;
}
}
points += pointsTemp;
return points;
}
}
You are not using the value returned by the parseSentence method.
Edit: I tried to rewrite this to be as close your original code with making the changes I feel where necessary.
Now Obviously your teacher has requirements and we can't go against that, but some points of interest you should keep in mind.
Multi Splitting
In your example you split the text to get the amount of words. Then instead of looping the already split text. You are sending the original input and then splitting it again. The "Double" splitting is why you needed "three" methods. If you don't double split you can simply loop the length from the single split and just use a single ParseWord method.
Deducting Values
In your example you take away 100 if the player overshot. The problem with this is let's say the person received a score like 200. Then it would loop twice to lower the value submitting the "You overshot message" twice. However let's say by some magical way a score of 100,000,000 was received. Then as you can see we would loop 1 million times to deduct this value essentially creating an not infinite but might as well be infinite loop.
To resolve this problem we simply do the below.
Value = Value % 100.
This will give us the remainder of our Value between 0 and 99. I.e. 167 will equal 67 and 12384 will be equal 84.
Using String (IndexOf)
What this does is takes the Character you provided and loop iterates over the String you provided. The worst case is 12 loops. There's also a lot of other stuff String and IndexOf do that is extra work and I recommend staying away from it if you can.
The alternative solution which I did is take the character and use " | 32" on it. I'm not going to go deep into how bits work, but basically these characters are 8 bit values but we only use 7 of it's bits ranging from 32 to 127. The amount of bits is like the power of 2. so 2^7 = 128 and 2^8 = 256. When we perform the "|" we are turning a bit on so if it's already on it won't change the value.
So in our example let's say we have the value 64.
This is bit 6 turned on. Now we want to turn on bit 5 "32" so the value becomes 96, but if we already had the value 96 and we turn bit 32 on it will still be 32.
Full List of ASCII Characters..
https://www.ascii-code.com/
The Game Loop
In your example you created "TWO" game loops the first one is when you start off, but once you overshot your score you enter the second loop and forget the first one. The problem is now your "Enter Words" and "You Undershot" code are never used anymore. So all someone will see is the line to enter text with no information on what to do or what occurred unless they overshot then they get the overshot message.
To fix this I made a single Game Loop which processes until the code ends via the SCORE == 100. You can see in the code that we begin every game loop with "Enter Words: " and parse the sentence. Then we add up our score and compare. If we undershot we simply restart the loop and try again. If we overshot we reduce the score and try again. If we succeeded we prompt the user if they would like to play again or end the game. Playing again will set the SCORE to 0 and start over the loop. Ending the game will "BREAK" the loop and cause it to end.
The Full Working Code With Recommended Changes
Feel free to comment if you need additional assistance.
import java.util.*;
public class WordGolf1
{
private static int SCORE = 0;
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
while (true)
{
System.out.print("\n\nEnter word: ");
ParseSentence(sc.nextLine());
if (SCORE == 100)
{
System.out.print("\nYou Won! Would you like to play again: Y/N?");
if ((sc.nextLine().charAt(0) | 32) == 'y')
{
SCORE = 0;
System.out.print("\nResetting Game...");
} else {
break;
}
}
else
{
if (SCORE > 100)
{
int overshot = SCORE - 100;
SCORE = SCORE % 100;
System.out.print("\nYou Overshot By " + overshot + " Points. You now have " + SCORE + " points.");
} else {
System.out.print("\nYou currently have " + SCORE + " points you need " + (100 - SCORE) + " more.");
}
}
}
}
private static int ParseSentence(String input)
{
String[] split = input.split(" ");
for (Strng s : input)
SCORE += ParseWord(s);
}
private static int ParseWord(String word)
{
int value = 1;
for (int i = 0; i < word.length(); ++i)
{
int c = (int)word.charAt(i) | 32;
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u')
{
value *= 2;
} else {
value += 1;
}
}
return value;
}
}
So I just whipped up this quick little demo game in like 30 minutes and I was wondering 2 things:
How could I organize my code more?
Would you be willing to play a game like this?
I know that I could use classes but I'm a bit inexperienced them. I'm confused on how to get variables from specific classes. Would I need to import them into the main method class?
import java.util.Scanner;
public class mainGame
{
public static Scanner kboard = new Scanner(System.in);
public static boolean loop = true;
public static int treesInArea = 0;
public static int day = 0;
public static int wood = 0;
public static int woodCollected = 0;
public static int woodLevel = 0;
public static void main(String[] args)
{
System.out.println("__________________________________");
System.out.println(" Welcome to seul...Lets begin ");
System.out.println(" You woke up in the middle of ");
System.out.println(" a forest. Use the command walk ");
System.out.println(" in order to walk into a new area ");
System.out.println("__________________________________\n");
while(loop == true)
{
String choice = kboard.nextLine();
if(choice.equalsIgnoreCase("walk"))
{
treesInArea = (int)(Math.random() * 20);
System.out.println("__________________________________");
System.out.println("The number of trees in this area is");
System.out.println(treesInArea + " trees");
System.out.println("__________________________________\n");
day++;
System.out.println(" It is day " + day + " ");
System.out.println("__________________________________\n");
System.out.println(" Current usuable commands are : ");
System.out.println(" - Chop tree\n");
} else
if(choice.equalsIgnoreCase("choptree") || choice.equalsIgnoreCase("chop tree"))
{
if(treesInArea < 1)
{
System.out.println("There are no trees in this area.");
} else
{
woodCollected = (int)(Math.random() * 10);
treesInArea --;
wood += woodCollected;
woodLevel += (int)(Math.random() * 2);
System.out.println("__________________________________");
System.out.println(" You collected " + woodCollected + " wood");
System.out.println(" Your total wood = " + wood);
System.out.println(" Your total woodcutting level = " + woodLevel);
System.out.println("__________________________________\n");
}
}
}
}
}
You could improve your code in 4 main ways:
1 • Your code-indentation is not great, it should be a 4 space(or just press tab) indent after class name, loops, if statements etc. Example:
private methodName() {
for(int i = 0; i < 10; i++;) {
//do something
}
}
2 • It is easier to read your code when braces are right after methods/loops, and it takes less space, such as(5 lines of neat code):
if (condition = true) {
//do something
} else {
//do something else
}
Rather than(7 lines of messy code):
if (condition = true)
{
//do something
} else
{
//do something else
}
because when you have long if-else blocks, or long loops, it can become hard to read.
3 • You do not need to add spaces after a line, this does nothing. So this:
System.out.println(" It is day " + day + " ");
Can become this:
System.out.println(" It is day " + day);
4 • Lastly, the best way to organize code is by "dividing and conquering". This means make methods even if they're very short, to prevent repeat-code and save time. For example, you printed this line: System.out.println("__________________________________"); 7 times in your program. If you make a method like the one below, you can save time and space, by avoiding repeat-code, and simply call the method using printDivider(); wherever you used this line:
private static void printDivider() {
System.out.println("__________________________________");
}
Yes, I would play your game(in fact i did play your game), but you could improve it, by adding more possibilities, or different 'paths' to go down, ending in different results.
So I need to create one class with an enum in it, then use a second class to randomly select one of those enum values and do that as many times as the user wants.
Here's the main code
while (loop){
System.out.println("Enter the number of times you want to toss the coin, enter '0' to end the program: ");
num = s.nextInt();
int tails = 0;
int heads = 0;
if (num == 0){
loop = false;
continue;
}
else if (num < 0){
System.out.println("That's a negative number");
continue;
}
for (int count = 0; count < num; count++)
{
if (rand.nextInt(2) == 0)
tails = tails + 1;
else
heads = heads + 1;
}
System.out.println("Heads: " + heads + " Tails: " + tails);
}
and then here's the enum code
public class Coin{
public enum CoinEnum {
HEADS,TAILS;
}
}
I cut out some stuff because it was unneeded.
I think I have the general idea on how to randomly select, you can see I already wrote a quick calculation on how to do if there wasn't an enum value but I have no idea how to access the enum values from my main program, I tried making the class a package but that didn't work, I'm just not sure how. Any help would be great.
Thanks
The following code should work - randomly generate HEAD or TAIL enum; comments added to the code. Edited to change this to a working standalone example.
public class CoinEnumDemo {
public static void main(String[] args) {
// print 10 random values
for (int i = 0; i < 10; i++) {
int val = (int) Math.round(Math.random());
System.out.println(CoinEnum.values()[val]);
}
}
enum CoinEnum {
HEAD, TAIL;
}
}
Why does the second while loop while (numberOfTries < 2) cancel both while loops? It runs perfect if there is no incorrect answer. But let's say I select 4 problems to be made, and I am only on the first problem. I give the incorrect answer 2 times so the program should say Incorrect two times and then give me a new question because while (numberOfTries < 2) should force it to break from that loop. But it doesn't, it just quits the whole thing. I know it has to be a logic issue, so what am I missing?
import java.util.Random;
import java.util.Scanner;
public class Howthe {
public static void main(String[] args) {
// Open Scanner
Scanner scan = new Scanner(System.in);
// Ask user to choose number of problems to be made.
// Can only choose 4, 9, or 16
System.out.print("Choose a number of problems to be made (4, 9, 16): ");
int userChoiceOfProblems = scan.nextInt();
// Ask user to choose a number between 0 and 12
System.out.print("\nChoose a number between 0 and 12: ");
int userNumberBetween0and12 = scan.nextInt();
// Ask user to choose between add/sub or multiply/divide
System.out.println("\nChoose to:"
+ "\n0: add/sub your chosen number"
+ " and the randomly generated number: "
+ "\n1: multiply/divide your chosen number"
+ " and the randomly generated number: ");
int userArithmeticChoice = scan.nextInt();
int counter = 0;
String equationString;
int equationAnswer;
int numberOfAnswersRight = 0;
int numberOfTries = 0;
int userAnswerToQuestion;
if (userArithmeticChoice == 0){
while (counter < userChoiceOfProblems){
// Create random number to decide if add or sub used.
// add is equal to 0 and sub is equal to 1
Random rand = new Random();
int randomNumberBetween0and1 = rand.nextInt(1) + 0;
// Create random number that is multiplied by userNumberBetween0and12
int randomNumberBetween0and12 = rand.nextInt(12) + 0;
// Add and increase counter by 1
if (randomNumberBetween0and1 == 0){
// If numberOfTries is more than 2, then display answer.
while (numberOfTries < 2){
// Compute the right answer (addition).
equationAnswer = userNumberBetween0and12 + randomNumberBetween0and12;
// Produce string of equation, then display string (addition).
equationString = userNumberBetween0and12 + " + "
+ randomNumberBetween0and12;
System.out.println(equationString);
userAnswerToQuestion = scan.nextInt();
// If answer is right, increase numberOfAnswersRight.
if (userAnswerToQuestion == equationAnswer){
numberOfAnswersRight++;
System.out.println("Correct!");
break;
}
// If answer is wrong, continue loop and increase numberOfTries
else if (userAnswerToQuestion != equationAnswer){
numberOfTries++;
System.out.println("Incorrect");
}
} // end of while (numberOfTries < 2 && !quit)
counter++;
}
} System.out.println("Yout got " + numberOfAnswersRight + " problem(s) right!");
}
}
}
numberOfTries is initialized outside of your loops. Once you try twice, it never gets set back to 0 which causes the loops to skip and finish on the next question because numberOfTries is already 2.