I've been working on a side project in Java in order to become more familiar with threads. It's a cookie clicker clone that uses different threads for the clicker, the counter, and everything else. Upon trying to run the final build I get an infinite recursion error resulting in stack overflow at one of my class initialization. I've tried moving the class to its own file, but that doesn't seem to change anything. Here's the code:
Game.java
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import java.awt.BorderLayout;
import java.awt.Dimension;
public class Game { // main class
// Integer
int total = 0;
int perSec = 0;
int perClick = 1;
int upgradeIndex = 0;
// Boolean
boolean running = false;
// JFrame
JFrame window;
// JTextField
JTextField totalDisplay;
// JButton
JButton clickArea;
JButton upgrade1;
JButton upgrade2;
JButton upgrade3;
JButton nextPage;
JButton prevPage;
// JPanel
JPanel clickWindow;
JPanel upgrades;
JPanel navigation;
// Upgrades
// Click upgrade
PerClickUpgrade exclamation; // amount 1 cost 10
PerClickUpgrade at; // amount 10 cost 100
PerClickUpgrade hashtag; // amount 100 cost 1000
PerClickUpgrade dollar; // amount 1000 cost 10000
// Sec upgrade
PerSecUpgrade percent; // amount 10 cost 1000
PerSecUpgrade carrot; // amount 100 cost 10000
PerSecUpgrade and; // amount 1000 cost 100000
// Click multiplier
PerClickMultiplier asterisk; // amount 2x cost 1000000
// Sec multiplier
PerSecMultiplier tilde; // amount 2x cost 10000000
Game() {
window = new JFrame(); // main window
clickWindow = new JPanel(); // panel for click button
upgrades = new JPanel(); // panel for upgrades
upgrade1 = new JButton();
upgrade2 = new JButton();
upgrade3 = new JButton();
navigation = new JPanel(); // navigate upgrade menu
nextPage = new JButton();
prevPage = new JButton();
exclamation = new PerClickUpgrade("!", 1, 10, upgrade1);
at = new PerClickUpgrade("@", 10, 100, upgrade2);
hashtag = new PerClickUpgrade("#",100,1000,upgrade3);
dollar = new PerClickUpgrade("$",1000,10000,upgrade1);
percent = new PerSecUpgrade("%",10,1000,upgrade2);
carrot = new PerSecUpgrade("^",100,10000, upgrade3);
and = new PerSecUpgrade("&", 1000,100000, upgrade1);
asterisk = new PerClickMultiplier("*",2,1000000, upgrade2);
tilde = new PerSecMultiplier("~", 2, 10000000, upgrade3);
window.setSize(new Dimension(800, 600));
window.setResizable(false);
window.setLocationRelativeTo(null);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setTitle("ASCII Clicker");
window.setLayout(new BorderLayout());
clickWindow.setPreferredSize(new Dimension((800 / 2), 600));
upgrades.setPreferredSize(new Dimension((800 / 2), 600));
navigation.setPreferredSize(new Dimension((800 / 2), (600 / 4)));
clickWindow.setLayout(new BorderLayout());
navigation.setLayout(new BorderLayout());
window.add(clickWindow, BorderLayout.WEST);
window.add(upgrades, BorderLayout.EAST);
clickWindow.add(totalDisplay, BorderLayout.PAGE_START);
clickWindow.add(clickArea, BorderLayout.CENTER);
upgrades.add(upgrade1);
upgrades.add(upgrade2);
upgrades.add(upgrade3);
upgrades.add(navigation);
navigation.add(nextPage, BorderLayout.EAST);
navigation.add(prevPage, BorderLayout.WEST);
window.pack();
Thread clickDetection = new Thread(new Clicker()); // detects clicks
Thread counter = new Thread(new Counter()); // counts total
clickDetection.start();
counter.start();
window.setVisible(true);
exclamation.update(); // sets exclamation, at, and hashtag as displayed upgrades
at.update();
hashtag.update();
upgrade1.addActionListener(e -> {
switch (upgradeIndex) {
case 0 -> exclamation.buy();
case 1 -> dollar.buy();
case 2 -> and.buy();
}
});
upgrade2.addActionListener(e -> {
switch (upgradeIndex) {
case 0 -> at.buy();
case 1 -> percent.buy();
case 2 -> asterisk.buy();
}
});
upgrade3.addActionListener(e -> {
switch (upgradeIndex) {
case 0 -> hashtag.buy();
case 1 -> carrot.buy();
case 2 -> tilde.buy();
}
});
prevPage.addActionListener(e -> {
if (upgradeIndex != 0) {
upgradeIndex--;
switch (upgradeIndex) {
case 0 -> {
exclamation.update();
at.update();
hashtag.update();
}
case 1 -> {
dollar.update();
percent.update();
carrot.update();
}
case 2 -> {
and.update();
asterisk.update();
tilde.update();
}
}
}
});
nextPage.addActionListener(e -> {
if (upgradeIndex != 2) {
upgradeIndex++;
switch (upgradeIndex) {
case 0 -> {
exclamation.update();
at.update();
hashtag.update();
}
case 1 -> {
dollar.update();
percent.update();
carrot.update();
}
case 2 -> {
and.update();
asterisk.update();
tilde.update();
}
}
}
});
}
public static void main(String[] args) {
new Game();
}
}
class Counter extends Game implements Runnable { // Updates counter
@Override
public void run() {
running = true;
while (running) {
total += (perSec / 10);
totalDisplay.setText(String.valueOf(total));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Clicker extends Game implements Runnable { // Records clicks
@Override
public void run() {
clickArea.setText("()");
clickArea.addActionListener(e -> total += perClick);
}
}
PerClickUpgrade.java
class PerClickUpgrade extends Game {
private int totalUpgrade = 0;
private int totalCost;
private final int upgradeAmount;
private final int cost;
private int timesBought = 0;
private final JButton display;
private final String symbol;
PerClickUpgrade(String symbol, int upgradeAmount, int cost, JButton display) {
this.symbol = symbol;
this.totalCost = cost;
this.cost = cost;
this.upgradeAmount = upgradeAmount;
this.display = display;
}
public void update() {
this.display.setText(
this.symbol +
"\nOwned: " +
this.timesBought +
"\nPer Click Increase: " +
this.totalUpgrade +
"\nCost: " +
this.totalCost
);
}
public void buy() {
if (total >= this.totalCost) {
this.timesBought++;
this.totalUpgrade += this.upgradeAmount;
perClick = this.totalUpgrade;
total -= this.cost;
this.totalCost += (this.cost / 2);
this.update();
}
}
}
PerSecUpgrade.java
class PerSecUpgrade extends Game {
private int totalUpgrade = 0;
private int totalCost;
private final int upgradeAmount;
private final int cost;
private int timesBought = 0;
private final JButton display;
private final String symbol;
PerSecUpgrade(String symbol, int upgradeAmount, int cost, JButton display) {
this.symbol = symbol;
this.totalCost = cost;
this.cost = cost;
this.upgradeAmount = upgradeAmount;
this.display = display;
}
void update() {
this.display.setText(
this.symbol +
"\nOwned: " +
this.timesBought +
"\nPer Click Increase: " +
this.totalUpgrade +
"\nCost: " +
this.totalCost
);
}
void buy() {
if (total >= this.totalCost) {
this.timesBought++;
this.totalUpgrade += this.upgradeAmount;
perSec = this.totalUpgrade;
total -= this.cost;
this.totalCost += (this.cost / 2);
this.update();
}
}
}
PerSecMultiplier.java
class PerSecMultiplier extends Game {
private int totalCost;
private final int upgradeAmount;
private final int cost;
private int timesBought = 0;
private final JButton display;
private final String symbol;
PerSecMultiplier(String symbol, int upgradeAmount, int cost, JButton display) {
this.symbol = symbol;
this.totalCost = cost;
this.cost = cost;
this.upgradeAmount = upgradeAmount;
this.display = display;
}
void update() {
this.display.setText(
this.symbol +
"\nOwned: " +
this.timesBought +
"\n" +
this.upgradeAmount +
"x Per Sec\nCost: " +
this.cost
);
}
void buy() {
if (total >= this.totalCost) {
this.timesBought++;
perSec *= this.upgradeAmount;
total -= this.totalCost;
this.totalCost += (this.cost / 2);
this.update();
}
}
}
PerClickMultiplier
public class PerClickMultiplier extends Game {
private final int upgradeAmount;
private final int cost;
private final JButton display;
private final String symbol;
private int totalCost;
private int timesBought = 0;
PerClickMultiplier(String symbol, int upgradeAmount, int cost, JButton display) {
this.totalCost = cost;
this.upgradeAmount = upgradeAmount;
this.cost = cost;
this.display = display;
this.symbol = symbol;
}
void update() {
this.display.setText(
this.symbol +
"\nOwned: " +
this.timesBought +
"\n" +
this.upgradeAmount +
"x Per Click\nCost: " +
this.cost
);
}
void buy() {
if (total >= this.totalCost) {
this.timesBought++;
perClick *= this.upgradeAmount;
total -= this.totalCost;
this.totalCost += (this.cost / 2);
this.update();
}
}
}
Any other general criticism is appreciated as well.