Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Checkers JavaFX

Dwuosobowa gra w warcaby napisana w Javie z użyciem JavaFX.

## Zasady gry

- Gra toczy się na szachownicy 8×8.
- **Gracz 1** (białe pionki) zaczyna jako pierwszy i porusza się w górę planszy.
- **Gracz 2** (czerwone pionki) porusza się w dół planszy.
- Pionki poruszają się o jedno pole po skosie na wolne, ciemne pole.
- Bicie jest **obowiązkowe** — jeśli istnieje możliwość bicia, trzeba z niej skorzystać.
- Bicie wielokrotne: jeśli po biciu pionek może bić kolejny, musi kontynuować serię.
- Pionek, który dotrze do ostatniego rzędu po stronie przeciwnika, staje się **damką** — może poruszać się po skosie w obu kierunkach.
- Gracz, który zbije wszystkie pionki przeciwnika, wygrywa.

## Sterowanie

Pionki przeciąga się myszką (drag & drop) na docelowe pole.
Jeśli ruch jest niedozwolony, pionek wraca na swoje miejsce.

## Wymagania

- **Java 18+**
- **Maven 3.8+**

## Uruchomienie

```bash
mvn clean javafx:run
```

## Struktura projektu

```
src/main/
├── java/com/example/checkers/
│ ├── CheckersApp.java — punkt wejścia, logika gry i planszy
│ ├── Controller.java — kontroler FXML (liczniki, timer, komunikat o zwycięstwie)
│ ├── Piece.java — pionek z obsługą drag & drop
│ ├── Tile.java — pole planszy
│ ├── PieceType.java — typ pionka (RED, WHITE, REDKING, WHITEKING)
│ ├── MoveType.java — wynik ruchu (NONE, NORMAL, CAPTURE)
│ ├── MoveResult.java — wrapper wyniku ruchu wraz z bitym pionkiem
│ └── StopWatch.java — stoper odmierzający czas od ostatniego ruchu
└── resources/com/example/checkers/
├── Board.fxml — układ głównego okna
├── RedPiece.fxml — wygląd czerwonego pionka
├── WhitePiece.fxml — wygląd białego pionka
├── RedKingPiece.fxml — wygląd czerwonej damki
└── WhiteKingPiece.fxml — wygląd białej damki
```

## Technologie

| Technologia | Wersja |
|---|---|
| Java | 18 |
| JavaFX | 18-ea+6 |
| Maven | 3.8.1 |
| JUnit Jupiter | 5.8.1 |
354 changes: 145 additions & 209 deletions src/main/java/com/example/checkers/CheckersApp.java

Large diffs are not rendered by default.

47 changes: 26 additions & 21 deletions src/main/java/com/example/checkers/Controller.java
Original file line number Diff line number Diff line change
@@ -1,38 +1,43 @@
package com.example.checkers;

import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;

import java.net.URL;
import java.util.ResourceBundle;

public class Controller implements Initializable{
@FXML
public Label whiteLabel = new Label();
@FXML
public Label redLabel = new Label();
public class Controller implements Initializable {
@FXML private Label redLabel;
@FXML private Label whiteLabel;
@FXML private Label timerLabel;
@FXML private Label winLabel;

private int redCount = 12;
private int whiteCount = 12;

public int redCounter=12;
public int whiteCounter=12;
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
redLabel.setText("12");
whiteLabel.setText("12");
}

public void bindTimer(StringProperty timerText) {
timerLabel.textProperty().bind(timerText);
}

public void decrementRed(){
redCounter--;
public void decrementRed() {
redLabel.setText(String.valueOf(--redCount));
if (redCount == 0) winLabel.setText("Gracz 2 wygrał!");
}
public void decrementWhite(){whiteCounter--;}
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
whiteLabel.setText(Integer.toString(whiteCounter=12));
redLabel.setText(Integer.toString(redCounter=12));

public void decrementWhite() {
whiteLabel.setText(String.valueOf(--whiteCount));
if (whiteCount == 0) winLabel.setText("Gracz 1 wygrał!");
}
public String playerWon() {
if (redCounter == 0) {
return "Gracz 2 wygral";
}else if (whiteCounter ==0){
return "Gracz 1 wygral";
}
else return "";

public boolean isGameOver() {
return redCount == 0 || whiteCount == 0;
}
}
22 changes: 10 additions & 12 deletions src/main/java/com/example/checkers/MoveResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,22 @@

public class MoveResult {
private final MoveType type;
private final Piece piece;

public MoveType getType() {
return type;
}


public Piece getPiece() {
return piece;
}
private final Piece capturedPiece;

public MoveResult(MoveType type) {
this(type, null);
}

public MoveResult(MoveType type, Piece piece) {
public MoveResult(MoveType type, Piece capturedPiece) {
this.type = type;
this.piece = piece;
this.capturedPiece = capturedPiece;
}

public MoveType getType() {
return type;
}

public Piece getCapturedPiece() {
return capturedPiece;
}
}
2 changes: 1 addition & 1 deletion src/main/java/com/example/checkers/MoveType.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.example.checkers;

public enum MoveType {
NONE, NORMAL, KILL
NONE, NORMAL, CAPTURE
}
81 changes: 57 additions & 24 deletions src/main/java/com/example/checkers/Piece.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.example.checkers;

import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.effect.DropShadow;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Ellipse;
import javafx.scene.paint.Color;

import java.io.IOException;
import java.net.URL;
import java.util.Objects;

import static com.example.checkers.CheckersApp.TILE_SIZE;
Expand All @@ -14,37 +17,49 @@ public class Piece extends StackPane {
private double mouseX, mouseY;
private double oldX, oldY;

public double getOldX() {
return oldX;
}
public Piece(PieceType type, int x, int y) throws IOException {
this.type = type;

public double getOldY() {
return oldY;
}
// Force the StackPane to exactly fill its tile so the visual is
// centred inside the square (otherwise it shrinks to the inner
// Group's bounds and renders in the top-left corner).
setMinSize(TILE_SIZE, TILE_SIZE);
setPrefSize(TILE_SIZE, TILE_SIZE);
setMaxSize(TILE_SIZE, TILE_SIZE);
setPickOnBounds(false); // hover/click only on visible pixels

PieceType getType() {
return type;
}
move(x, y);

public void setType(PieceType type) {
this.type = type;
}
String fxmlName = switch (type) {
case RED -> "RedPiece.fxml";
case WHITE -> "WhitePiece.fxml";
case REDKING -> "RedKingPiece.fxml";
case WHITEKING -> "WhiteKingPiece.fxml";
};

public Piece(PieceType type, int x, int y) throws IOException {
this.type = type;
move(x, y);
Ellipse bg;
switch (type) {
case RED -> bg = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("RedPiece.fxml")));
case WHITE -> bg = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("WhitePiece.fxml")));
default -> bg = null;
}

getChildren().add(bg);
Node visual = FXMLLoader.load(Objects.requireNonNull(getClass().getResource(fxmlName)));
getChildren().add(visual);

// Hover glow
setOnMouseEntered(e -> {
if (getScaleX() == 1.0)
setEffect(new DropShadow(14, Color.web("#ffffff55")));
});
setOnMouseExited(e -> {
if (getScaleX() == 1.0)
setEffect(null);
});

// Scale-up + shadow on press
setOnMousePressed(e -> {
mouseX = e.getSceneX();
mouseY = e.getSceneY();
setScaleX(1.13);
setScaleY(1.13);
setEffect(new DropShadow(20, Color.web("#00000099")));
toFront();
});

setOnMouseDragged(e -> relocate(e.getSceneX() - mouseX + oldX, e.getSceneY() - mouseY + oldY));
}

Expand All @@ -56,5 +71,23 @@ public void move(int x, int y) {

public void abortMove() {
relocate(oldX, oldY);
resetScale();
}

public void resetScale() {
setScaleX(1.0);
setScaleY(1.0);
setEffect(null);
}

public void promoteToKing(PieceType kingType, URL fxmlUrl) throws IOException {
this.type = kingType;
Node kingVisual = FXMLLoader.load(Objects.requireNonNull(fxmlUrl));
getChildren().set(0, kingVisual);
}

public PieceType getType() { return type; }
public void setType(PieceType type) { this.type = type; }
public double getOldX() { return oldX; }
public double getOldY() { return oldY; }
}
17 changes: 12 additions & 5 deletions src/main/java/com/example/checkers/PieceType.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package com.example.checkers;

public enum PieceType {
RED(1, 0), WHITE(-1, 0), REDKING(1,-1), WHITEKING(-1, 1), ;
RED(1, 0),
WHITE(-1, 0),
REDKING(1, -1),
WHITEKING(-1, 1);

final int moveDir;
public int isKing;
final int secondaryDir; // reverse diagonal direction for kings; 0 for regular pieces

PieceType(int moveDIR, int isKing){
this.moveDir = moveDIR;
this.isKing = isKing;
PieceType(int moveDir, int secondaryDir) {
this.moveDir = moveDir;
this.secondaryDir = secondaryDir;
}

public boolean isKing() {
return secondaryDir != 0;
}
}
50 changes: 27 additions & 23 deletions src/main/java/com/example/checkers/StopWatch.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,46 @@
import javafx.animation.Timeline;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;

public class StopWatch extends StackPane {
StringProperty text = new SimpleStringProperty();
Timeline timeline;
int mins = 0, secs = 0, millis = 0;


void change(StringProperty text) {
if (millis == 1000) {
secs++;
millis = 0;
}
if (secs == 60) {
mins++;
secs = 0;
}
text.set(((((mins / 10) == 0) ? "0" : "") + mins + ":"
+ (((secs / 10) == 0) ? "0" : "") + secs + ":"
+ (((millis / 10) == 0) ? "00" : (((millis / 100) == 0) ? "0" : "")) + millis++));
}
public class StopWatch {
private final StringProperty text = new SimpleStringProperty("00:00:000");
private final Timeline timeline;
private int mins, secs, millis;

public StopWatch() {

timeline = new Timeline(new KeyFrame(Duration.millis(1), event -> change(text)));
timeline = new Timeline(new KeyFrame(Duration.millis(1), e -> tick()));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(false);
}

public void start() {
timeline.playFromStart();
}

void reset() {
public void stop() {
timeline.stop();
}

public void reset() {
mins = 0;
secs = 0;
millis = 0;
}

public StringProperty textProperty() {
return text;
}

private void tick() {
if (millis == 1000) {
secs++;
millis = 0;
}
if (secs == 60) {
mins++;
secs = 0;
}
text.set(String.format("%02d:%02d:%03d", mins, secs, millis++));
}
}
Loading