Skip to content

Commit 2bbbf8b

Browse files
committed
feat: implement game state saving and loading functionality
1 parent 5670979 commit 2bbbf8b

File tree

9 files changed

+509
-10
lines changed

9 files changed

+509
-10
lines changed

client/src/main/java/io/exterminator3618/client/Constants.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,9 @@ public enum GameState {
136136
public static final int PLAY_AREA_X_MIN = 55;
137137
public static final int PLAY_AREA_Y_MAX = 985;
138138
public static final int PLAY_AREA_Y_MIN = 85;
139+
140+
/**
141+
* Saved game path
142+
*/
143+
public static final String SAVE_FILE = "client/src/main/resources/data/save/saved_game.json";
139144
}

client/src/main/java/io/exterminator3618/client/components/Ball.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,4 +253,8 @@ public void setBallSpeed(float speedMultiplier) {
253253
//updateVelocity();
254254
}
255255

256+
public void setComboCount(int comboCount) {
257+
this.comboCount = comboCount;
258+
}
259+
256260
}

client/src/main/java/io/exterminator3618/client/components/PowerUp.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ public float getDuration() {
4343
return duration;
4444
}
4545

46+
public void setRemainingDuration(float duration) {
47+
this.remainingDuration = duration;
48+
}
49+
4650
public abstract void applyEffect(GameScreen screen);
4751
public abstract void removeEffect(GameScreen screen);
4852

client/src/main/java/io/exterminator3618/client/screens/GameScreen.java

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,23 @@
3535
import static io.exterminator3618.client.Physics.checkPowerUpCollision;
3636
import io.exterminator3618.client.components.Ball;
3737
import io.exterminator3618.client.components.Brick;
38+
import io.exterminator3618.client.components.ExtraLifePowerUp;
39+
import io.exterminator3618.client.components.HeavyBallPowerUp;
40+
import io.exterminator3618.client.components.MultiBallBrick;
41+
import io.exterminator3618.client.components.NormalBrick;
3842
import io.exterminator3618.client.components.Paddle;
3943
import io.exterminator3618.client.components.PowerUp;
4044
import io.exterminator3618.client.components.PowerUpBrick;
45+
import io.exterminator3618.client.components.SlowBallPowerUp;
46+
import io.exterminator3618.client.components.SolidBrick;
47+
import io.exterminator3618.client.components.SplitBallPowerUp;
48+
import io.exterminator3618.client.components.StickyPaddlePowerUp;
4149
import io.exterminator3618.client.components.StrongBrick;
4250
import io.exterminator3618.client.components.TextButton;
51+
import io.exterminator3618.client.components.WidenPaddlePowerUp;
4352
import io.exterminator3618.client.managers.SoundManager;
4453
import io.exterminator3618.client.utils.Assets;
54+
import io.exterminator3618.client.utils.GameSaveData;
4555
import io.exterminator3618.client.utils.LevelLoader;
4656
import io.exterminator3618.client.utils.Renderer;
4757

@@ -128,6 +138,168 @@ public void loadLevel(int levelNumber, Ball oldball) {
128138
ball.setStuckToPaddle(true);
129139
}
130140

141+
142+
public GameSaveData.SaveData exportState() {
143+
GameSaveData.SaveData d = new GameSaveData.SaveData();
144+
d.level = currentLevel;
145+
d.score = score;
146+
d.lives = lives;
147+
148+
GameSaveData.Obj b = new GameSaveData.Obj();
149+
b.x = ball.getX();
150+
b.y = ball.getY();
151+
b.width = ball.getWidth();
152+
b.height = ball.getHeight();
153+
b.vx = ball.getVelocityX();
154+
b.vy = ball.getVelocityY();
155+
b.region = ball.getRegionName();
156+
b.bool1 = ball.isHeavyBall();
157+
b.bool2 = ball.isStuckToPaddle();
158+
b.i1 = ball.getStuckOffsetX();
159+
b.cb = ball.getComboCount();
160+
d.ball = b;
161+
162+
GameSaveData.Obj p = new GameSaveData.Obj();
163+
p.x = paddle.getX();
164+
p.y = paddle.getY();
165+
p.width = paddle.getWidth();
166+
p.height = paddle.getHeight();
167+
p.region = paddle.getRegionName();
168+
p.bool1 = paddle.isSticky();
169+
d.paddle = p;
170+
171+
d.extraBalls = new ArrayList<>();
172+
for (Ball eb : extraBalls) {
173+
GameSaveData.Obj o = new GameSaveData.Obj();
174+
o.x = eb.getX();
175+
o.y = eb.getY();
176+
o.width = eb.getWidth();
177+
o.height = eb.getHeight();
178+
o.vx = eb.getVelocityX();
179+
o.vy = eb.getVelocityY();
180+
o.region = eb.getRegionName();
181+
o.bool1 = eb.isHeavyBall();
182+
o.bool2 = eb.isStuckToPaddle();
183+
o.i1 = eb.getStuckOffsetX();
184+
d.extraBalls.add(o);
185+
}
186+
187+
d.bricks = new ArrayList<>();
188+
for (Brick bk : bricks) {
189+
if (bk.isDestroyed()) continue;
190+
GameSaveData.BrickState bs = new GameSaveData.BrickState();
191+
bs.x = bk.getX();
192+
bs.y = bk.getY();
193+
bs.width = bk.getWidth();
194+
bs.height = bk.getHeight();
195+
bs.region = bk.getRegionName();
196+
bs.hp = bk.getHitPoints();
197+
bs.type = bk.getType();
198+
d.bricks.add(bs);
199+
}
200+
201+
d.activePowerUps = new ArrayList<>();
202+
if (activePowerUps != null) {
203+
for (PowerUp apu : activePowerUps) {
204+
GameSaveData.PowerUpState ps = new GameSaveData.PowerUpState();
205+
ps.type = apu.getType();
206+
ps.remaining = apu.getRemainingDuration();
207+
d.activePowerUps.add(ps);
208+
}
209+
}
210+
return d;
211+
}
212+
213+
public void importState(GameSaveData.SaveData d) {
214+
this.currentLevel = Math.max(1, d.level);
215+
this.score = Math.max(0, d.score);
216+
setLives(d.lives);
217+
218+
// fresh lists
219+
powerUps = new ArrayList<>();
220+
activePowerUps = new ArrayList<>();
221+
extraBalls = new ArrayList<>();
222+
223+
// paddle
224+
paddle = new Paddle(d.paddle.x, d.paddle.y, d.paddle.width, d.paddle.height, d.paddle.region);
225+
paddle.setSticky(d.paddle.bool1);
226+
227+
// ball
228+
ball = new Ball(d.ball.x, d.ball.y, d.ball.width, d.ball.height, d.ball.region, BALL_SPEED, 90);
229+
ball.setVelocity(d.ball.vx, d.ball.vy);
230+
ball.setHeavyBall(d.ball.bool1);
231+
ball.setStuckToPaddle(d.ball.bool2);
232+
ball.setStuckOffsetX(d.ball.i1);
233+
ball.setComboCount(d.ball.cb);
234+
235+
// extra balls
236+
for (GameSaveData.Obj o : d.extraBalls) {
237+
Ball eb = new Ball(o.x, o.y, o.width, o.height, o.region, BALL_SPEED, 90);
238+
eb.setVelocity(o.vx, o.vy);
239+
eb.setHeavyBall(o.bool1);
240+
eb.setStuckToPaddle(o.bool2);
241+
eb.setStuckOffsetX(o.i1);
242+
extraBalls.add(eb);
243+
}
244+
245+
// bricks
246+
bricks = new ArrayList<>();
247+
for (GameSaveData.BrickState bs : d.bricks) {
248+
switch (bs.type) {
249+
case "solid_brick":
250+
Brick sb = new SolidBrick(bs.x, bs.y);
251+
bricks.add(sb);
252+
break;
253+
case "normal":
254+
Brick nb = new NormalBrick(bs.x, bs.y, bs.width, bs.height, bs.region);
255+
bricks.add(nb);
256+
break;
257+
case "multiball":
258+
Brick mb = new MultiBallBrick(bs.x, bs.y);
259+
bricks.add(mb);
260+
break;
261+
case "strong":
262+
Brick stb = new StrongBrick(bs.x, bs.y, bs.width, bs.height, bs.region, bs.hp);
263+
bricks.add(stb);
264+
break;
265+
default:
266+
// Unknown brick type; skip
267+
break;
268+
}
269+
}
270+
271+
// active powerups: re-apply with saved remaining duration
272+
if (d.activePowerUps != null) {
273+
for (GameSaveData.PowerUpState ps : d.activePowerUps) {
274+
PowerUp pu = createPowerUpByType(ps.type);
275+
if (pu != null) {
276+
pu.applyEffect(this);
277+
pu.setRemainingDuration(ps.remaining);
278+
activePowerUps.add(pu);
279+
}
280+
}
281+
}
282+
}
283+
284+
private PowerUp createPowerUpByType(String type) {
285+
switch (type) {
286+
case "Widen Paddle":
287+
return new WidenPaddlePowerUp(0, 0);
288+
case "Heavy Ball":
289+
return new HeavyBallPowerUp(0, 0);
290+
case "Sticky Paddle":
291+
return new StickyPaddlePowerUp(0, 0);
292+
case "Extra Life":
293+
return new ExtraLifePowerUp(0, 0);
294+
case "Split Ball":
295+
return new SplitBallPowerUp(0, 0);
296+
case "Slow Ball":
297+
return new SlowBallPowerUp(0, 0);
298+
default:
299+
return null;
300+
}
301+
}
302+
131303
@Override
132304
public void show() {
133305
camera = new OrthographicCamera();
@@ -523,6 +695,14 @@ public List<Ball> getExtraBalls() {
523695
return extraBalls;
524696
}
525697

698+
public int getScore() {
699+
return score;
700+
}
701+
702+
public int getCurrentLevel() {
703+
return currentLevel;
704+
}
705+
526706
public SoundManager getSoundManager() {
527707
return soundManager;
528708
}

client/src/main/java/io/exterminator3618/client/screens/MainMenuScreen.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.exterminator3618.client.managers.SoundManager;
1616
import io.exterminator3618.client.utils.Assets;
1717
import io.exterminator3618.client.utils.Renderer;
18+
import io.exterminator3618.client.utils.SaveManager;
1819

1920
public final class MainMenuScreen implements Screen {
2021

@@ -26,6 +27,7 @@ public final class MainMenuScreen implements Screen {
2627
private Viewport viewport;
2728

2829
private TextButton startButton;
30+
private TextButton continueButton;
2931
private TextButton settingsButton;
3032
private TextButton exitButton;
3133

@@ -42,9 +44,11 @@ public MainMenuScreen(Exterminator3618 game) {
4244

4345
final int CENTER_X = (Constants.WINDOW_WIDTH / 2) - (Constants.BUTTON_WIDTH / 2);
4446
final int START_Y = (Constants.WINDOW_HEIGHT / 2) + Constants.BUTTON_HEIGHT + PADDING - 200;
47+
final int CONTINUE_Y = START_Y - Constants.BUTTON_HEIGHT - PADDING;
4548
final int SETTINGS_Y = CONTINUE_Y - Constants.BUTTON_HEIGHT - PADDING;
4649
final int EXIT_Y = SETTINGS_Y - Constants.BUTTON_HEIGHT - PADDING;
4750
startButton = new TextButton("Start Game",CENTER_X, START_Y, Constants.BUTTON_WIDTH, Constants.BUTTON_HEIGHT, true);
51+
continueButton = new TextButton("Continue", CENTER_X, CONTINUE_Y, Constants.BUTTON_WIDTH, Constants.BUTTON_HEIGHT, true);
4852
settingsButton = new TextButton("Options", CENTER_X, SETTINGS_Y, Constants.BUTTON_WIDTH, Constants.BUTTON_HEIGHT, true);
4953
exitButton = new TextButton("Exit", CENTER_X, EXIT_Y, Constants.BUTTON_WIDTH, Constants.BUTTON_HEIGHT, true);
5054
}
@@ -69,6 +73,9 @@ public void render(float delta) {
6973
// Draw text here
7074
renderer.drawLogo(Constants.WINDOW_WIDTH / 2, Constants.WINDOW_HEIGHT / 2);
7175
startButton.draw(renderer);
76+
if (SaveManager.hasSave(game)) {
77+
continueButton.draw(renderer);
78+
}
7279
settingsButton.draw(renderer);
7380
exitButton.draw(renderer);
7481
renderer.end();
@@ -87,9 +94,17 @@ public void render(float delta) {
8794
viewport.unproject(touchPos);
8895

8996
if (startButton.isClicked(touchPos.x, touchPos.y)) {
97+
SaveManager.clearSave(game);
9098
game.launchScreen(new GameScreen(game));
9199
}
92100

101+
if (continueButton.isClicked(touchPos.x, touchPos.y)) {
102+
soundManager.stop();
103+
GameScreen gs = new GameScreen(game);
104+
SaveManager.loadInto(game, gs);
105+
game.launchScreen(gs);
106+
}
107+
93108
// Transition to setting screen on input
94109
if (settingsButton.isClicked(touchPos.x, touchPos.y)) {
95110
game.launchScreen(new SettingsScreen(game, this));

client/src/main/java/io/exterminator3618/client/screens/PauseScreen.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@
44
import com.badlogic.gdx.Input;
55
import com.badlogic.gdx.Screen;
66
import com.badlogic.gdx.graphics.GL20;
7-
87
import com.badlogic.gdx.graphics.OrthographicCamera;
8+
import com.badlogic.gdx.math.Vector3;
99
import com.badlogic.gdx.utils.viewport.FitViewport;
1010
import com.badlogic.gdx.utils.viewport.Viewport;
11-
import com.badlogic.gdx.math.Vector3;
11+
1212
import io.exterminator3618.client.Constants;
1313
import io.exterminator3618.client.Exterminator3618;
1414
import io.exterminator3618.client.components.Box;
1515
import io.exterminator3618.client.components.TextButton;
1616
import io.exterminator3618.client.utils.Renderer;
17+
import io.exterminator3618.client.utils.SaveManager;
1718

1819
public final class PauseScreen implements Screen {
1920

@@ -78,23 +79,18 @@ public void render(float v) {
7879
renderer.end();
7980
Gdx.gl.glDisable(GL20.GL_BLEND);
8081

81-
// Resume game on input (e.g., P key)
82-
if (Gdx.input.isKeyJustPressed(Input.Keys.P)) {
82+
// Resume game on input (e.g., P or ESC key) - use justPressed to avoid immediate close
83+
if (Gdx.input.isKeyJustPressed(Input.Keys.P) || Gdx.input.isKeyJustPressed(Input.Keys.ESCAPE)) {
8384
game.backToPreviousScreen();
84-
} else if (Gdx.input.isKeyJustPressed(Input.Keys.ESCAPE)) {
85-
// Stop gameplay background music before transitioning to game over
86-
gameScreen.getSoundManager().stop();
87-
game.launchScreen(new GameOverScreen(game));
88-
game.replaceCurrentScreen(new MainMenuScreen(game));
8985
}
9086

91-
9287
if (Gdx.input.justTouched()) {
9388
touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0);
9489

9590
viewport.unproject(touchPos);
9691

9792
if (backButton.isClicked(touchPos.x, touchPos.y)) {
93+
SaveManager.saveGame(game, gameScreen);
9894
game.replaceCurrentScreen(new MainMenuScreen(game));
9995
}
10096
if (playButton.isClicked(touchPos.x, touchPos.y)) {

0 commit comments

Comments
 (0)