diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..a1757ae
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..69ace3f
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..ce31086
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CodeKatas_20240507_CodeSmells.iml b/CodeKatas_20240507_CodeSmells.iml
new file mode 100644
index 0000000..7c56e11
--- /dev/null
+++ b/CodeKatas_20240507_CodeSmells.iml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java/CodeSmells.iml b/java/CodeSmells.iml
index d0c7dd5..f3fdec3 100644
--- a/java/CodeSmells.iml
+++ b/java/CodeSmells.iml
@@ -10,5 +10,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java/src/Board.java b/java/src/Board.java
index 014a39d..88500b6 100644
--- a/java/src/Board.java
+++ b/java/src/Board.java
@@ -1,42 +1,45 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
-public class Board
-{
- private List _plays = new ArrayList<>();
+public class Board {
+ private final List plays = new ArrayList<>();
- public Board()
- {
- for (int i = 0; i < 3; i++)
- {
- for (int j = 0; j < 3; j++)
- {
- Tile tile = new Tile();
- tile.X = i;
- tile.Y = j;
- tile.Symbol = ' ';
- _plays.add(tile);
+ public Board() {
+ for (int i = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++) {
+ Tile tile = new Tile(i, j);
+ plays.add(tile);
}
}
}
- public Tile TileAt(int x, int y)
- {
- for (Tile t : _plays) {
- if (t.X == x && t.Y == y){
- return t;
+ public Symbol getSymbolAt(int x, int y) {
+ return Optional.ofNullable(this.tileAt(x, y)).map(Tile::getSymbol).orElse(null);
+ }
+
+ public void setSymbolAt(int x, int y, Symbol symbol) {
+ Optional.ofNullable(this.tileAt(x, y)).ifPresent(t -> t.setSymbol(symbol));
+ }
+
+ public String print() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++) {
+ Tile currentTile = this.tileAt(i, j);
+ sb.append(Optional.ofNullable(currentTile).map(Tile::getSymbol).map(Symbol::getAsChar).orElse(' '));
}
+ sb.append("\n");
}
- return null;
+ return sb.toString();
}
- public void AddTileAt(char symbol, int x, int y)
- {
- Tile newTile = new Tile();
- newTile.X = x;
- newTile.Y = y;
- newTile.Symbol = symbol;
-
- TileAt(x,y).Symbol = symbol;
+ private Tile tileAt(int x, int y) {
+ for (Tile t : plays) {
+ if (t.getX() == x && t.getY() == y) {
+ return t;
+ }
+ }
+ return null;
}
-}
\ No newline at end of file
+}
diff --git a/java/src/Game.java b/java/src/Game.java
index bf4c95d..f52ef2b 100644
--- a/java/src/Game.java
+++ b/java/src/Game.java
@@ -1,69 +1,70 @@
+import java.util.Optional;
+
public class Game {
- private char _lastSymbol = ' ';
- private Board _board = new Board();
+ private Symbol lastSymbol = null;
+ private final Board board = new Board();
+
+ public void play(char rawSymbol, int x, int y) {
+ Symbol symbol = Symbol.fromChar(rawSymbol);
- public void Play(char symbol, int x, int y) throws Exception {
//if first move
- if (_lastSymbol == ' ') {
- //if player is X
- if (symbol == 'O') {
- throw new Exception("Invalid first player");
- }
+ if (lastSymbol == null && symbol == Symbol.O) {
+ throw new IllegalArgumentException("Invalid first player");
}
+
//if not first move but player repeated
- else if (symbol == _lastSymbol) {
- throw new Exception("Invalid next player");
+ else if (symbol.equals(lastSymbol)) {
+ throw new IllegalArgumentException("Invalid next player");
}
+
//if not first move but play on an already played tile
- else if (_board.TileAt(x, y).Symbol != ' ') {
- throw new Exception("Invalid position");
+ else if (board.getSymbolAt(x, y) != null) {
+ throw new IllegalArgumentException("Invalid position");
}
// update game state
- _lastSymbol = symbol;
- _board.AddTileAt(symbol, x, y);
+ lastSymbol = symbol;
+ board.setSymbolAt(x, y, symbol);
}
- public char Winner() {
+ public Optional computeWinner() {
//if the positions in first row are taken
- if (_board.TileAt(0, 0).Symbol != ' ' &&
- _board.TileAt(0, 1).Symbol != ' ' &&
- _board.TileAt(0, 2).Symbol != ' ') {
+ if (board.getSymbolAt(0, 0) != null &&
+ board.getSymbolAt(0, 1) != null &&
+ board.getSymbolAt(0, 2) != null) {
//if first row is full with same symbol
- if (_board.TileAt(0, 0).Symbol ==
- _board.TileAt(0, 1).Symbol &&
- _board.TileAt(0, 2).Symbol == _board.TileAt(0, 1).Symbol) {
- return _board.TileAt(0, 0).Symbol;
+ if (board.getSymbolAt(0, 0).equals(board.getSymbolAt(0, 1)) &&
+ board.getSymbolAt(0, 2).equals(board.getSymbolAt(0, 1))) {
+ return Optional.ofNullable(board.getSymbolAt(0, 0));
}
}
//if the positions in first row are taken
- if (_board.TileAt(1, 0).Symbol != ' ' &&
- _board.TileAt(1, 1).Symbol != ' ' &&
- _board.TileAt(1, 2).Symbol != ' ') {
+ if (board.getSymbolAt(1, 0) != null &&
+ board.getSymbolAt(1, 1) != null &&
+ board.getSymbolAt(1, 2) != null) {
//if middle row is full with same symbol
- if (_board.TileAt(1, 0).Symbol ==
- _board.TileAt(1, 1).Symbol &&
- _board.TileAt(1, 2).Symbol ==
- _board.TileAt(1, 1).Symbol) {
- return _board.TileAt(1, 0).Symbol;
+ if (board.getSymbolAt(1, 0).equals(board.getSymbolAt(1, 1)) &&
+ board.getSymbolAt(1, 2).equals(board.getSymbolAt(1, 1))) {
+ return Optional.ofNullable(board.getSymbolAt(1, 0));
}
}
//if the positions in first row are taken
- if (_board.TileAt(2, 0).Symbol != ' ' &&
- _board.TileAt(2, 1).Symbol != ' ' &&
- _board.TileAt(2, 2).Symbol != ' ') {
+ if (board.getSymbolAt(2, 0) != null &&
+ board.getSymbolAt(2, 1) != null &&
+ board.getSymbolAt(2, 2) != null) {
//if middle row is full with same symbol
- if (_board.TileAt(2, 0).Symbol ==
- _board.TileAt(2, 1).Symbol &&
- _board.TileAt(2, 2).Symbol ==
- _board.TileAt(2, 1).Symbol) {
- return _board.TileAt(2, 0).Symbol;
+ if (board.getSymbolAt(2, 0).equals(board.getSymbolAt(2, 1)) &&
+ board.getSymbolAt(2, 2).equals(board.getSymbolAt(2, 1))) {
+ return Optional.ofNullable(board.getSymbolAt(2, 0));
}
}
- return ' ';
+ return Optional.empty();
+ }
+
+ public String printBoard() {
+ return board.print();
}
}
-
diff --git a/java/src/Symbol.java b/java/src/Symbol.java
new file mode 100644
index 0000000..873833a
--- /dev/null
+++ b/java/src/Symbol.java
@@ -0,0 +1,20 @@
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+@AllArgsConstructor
+@Getter
+public enum Symbol {
+ X('X'),
+ O('O');
+
+ private final char asChar;
+
+ public static Symbol fromChar(char in) {
+ return Arrays.stream(Symbol.values())
+ .filter(s -> s.asChar == Character.toUpperCase(in))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Invalid symbol: %s".formatted(in)));
+ }
+}
diff --git a/java/src/Tile.java b/java/src/Tile.java
index 03be347..378a666 100644
--- a/java/src/Tile.java
+++ b/java/src/Tile.java
@@ -1,7 +1,12 @@
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
-public class Tile
-{
- public int X;
- public int Y;
- public char Symbol;
-}
\ No newline at end of file
+@Getter
+@RequiredArgsConstructor
+public class Tile {
+ private final int x;
+ private final int y;
+ @Setter
+ private Symbol symbol;
+}
diff --git a/java/test/GameTest.java b/java/test/GameTest.java
new file mode 100644
index 0000000..c1da8f6
--- /dev/null
+++ b/java/test/GameTest.java
@@ -0,0 +1,206 @@
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class GameTest {
+ private Game game;
+
+ @BeforeEach
+ void InitializeGame() {
+ game = new Game();
+ }
+
+ @Test
+ void NotAllowPlayerOToPlayFirst() {
+ assertThrows(IllegalArgumentException.class, () -> game.play('O', 0, 0));
+ }
+
+ @Test
+ void NotAllowPlayerXToPlayTwiceInARow() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ game.play('X', 0, 0);
+ game.play('X', 1, 0);
+ });
+ }
+
+ @Test
+ void NotAllowPlayerToPlayInLastPlayedPosition() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ game.play('X', 0, 0);
+ game.play('O', 0, 0);
+ });
+ }
+
+ @Test
+ void NotAllowPlayerToPlayInAnyPlayedPosition() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ game.play('X', 0, 0);
+ game.play('O', 1, 0);
+ game.play('X', 0, 0);
+ });
+ }
+
+ @Test
+ void DeclareNoWinnerWithUnfinishedGrid() {
+ game.play('X', 0, 0);
+ game.play('O', 1, 0);
+ game.play('X', 0, 1);
+ game.play('O', 1, 1);
+
+ Optional winner = game.computeWinner();
+
+ assertTrue(winner.isEmpty());
+ }
+
+ @Test
+ void DeclareNoWinnerWithTiedGrid() {
+ game.play('X', 1, 1);
+ game.play('O', 0, 0);
+ game.play('X', 0, 2);
+ game.play('O', 2, 0);
+ game.play('X', 1, 0);
+ game.play('O', 1, 2);
+ game.play('X', 2, 1);
+ game.play('O', 0, 1);
+ game.play('X', 2, 2);
+
+ Optional winner = game.computeWinner();
+
+ assertTrue(winner.isEmpty());
+ }
+
+ @Test
+ void DeclarePlayerXAsAWinnerIfThreeInTopRow() {
+ game.play('X', 0, 0);
+ game.play('O', 1, 0);
+ game.play('X', 0, 1);
+ game.play('O', 1, 1);
+ game.play('X', 0, 2);
+
+ Optional winner = game.computeWinner();
+
+ assertTrue(winner.isPresent());
+ assertEquals(Symbol.X, winner.get());
+ }
+
+ @Test
+ void DeclarePlayerOAsAWinnerIfThreeInTopRow() {
+ game.play('X', 2, 2);
+ game.play('O', 0, 0);
+ game.play('X', 1, 0);
+ game.play('O', 0, 1);
+ game.play('X', 1, 1);
+ game.play('O', 0, 2);
+
+ Optional winner = game.computeWinner();
+
+ assertTrue(winner.isPresent());
+ assertEquals(Symbol.O, winner.get());
+ }
+
+ @Test
+ void DeclarePlayerXAsAWinnerIfThreeInMiddleRow() {
+ game.play('X', 1, 0);
+ game.play('O', 0, 0);
+ game.play('X', 1, 1);
+ game.play('O', 0, 1);
+ game.play('X', 1, 2);
+
+ Optional winner = game.computeWinner();
+
+ assertTrue(winner.isPresent());
+ assertEquals(Symbol.X, winner.get());
+ }
+
+ @Test
+ void DeclarePlayerOAsAWinnerIfThreeInMiddleRow() {
+ game.play('X', 0, 0);
+ game.play('O', 1, 0);
+ game.play('X', 2, 0);
+ game.play('O', 1, 1);
+ game.play('X', 2, 1);
+ game.play('O', 1, 2);
+
+ Optional winner = game.computeWinner();
+
+ assertTrue(winner.isPresent());
+ assertEquals(Symbol.O, winner.get());
+ }
+
+ @Test
+ void DeclarePlayerXAsAWinnerIfThreeInBottomRow() {
+ game.play('X', 2, 0);
+ game.play('O', 0, 0);
+ game.play('X', 2, 1);
+ game.play('O', 0, 1);
+ game.play('X', 2, 2);
+
+ Optional winner = game.computeWinner();
+
+ assertTrue(winner.isPresent());
+ assertEquals(Symbol.X, winner.get());
+ }
+
+ @Test
+ void DeclarePlayerOAsAWinnerIfThreeInBottomRow() {
+ game.play('X', 0, 0);
+ game.play('O', 2, 0);
+ game.play('X', 1, 0);
+ game.play('O', 2, 1);
+ game.play('X', 1, 1);
+ game.play('O', 2, 2);
+
+ Optional winner = game.computeWinner();
+
+ assertTrue(winner.isPresent());
+ assertEquals(Symbol.O, winner.get());
+ }
+
+ @Test
+ void NotAllowPlayingIllegalSymbol() {
+ game.play('X', 0, 0);
+ game.play('O', 2, 0);
+ assertThrows(IllegalArgumentException.class, () -> game.play('Y', 1, 0));
+ }
+
+ @Test
+ void AllowPlayingLowercaseVariants() {
+ game.play('X', 0, 0);
+ game.play('O', 2, 0);
+ assertDoesNotThrow(() -> game.play('x', 1, 0));
+ assertDoesNotThrow(() -> game.play('o', 2, 1));
+ }
+
+ @Test
+ void DeclarePlayerXAsAWinnerIfThreeInLeftColumn() {
+ game.play('X', 0, 0);
+ game.play('O', 1, 0);
+ game.play('X', 0, 1);
+ game.play('O', 1, 1);
+ game.play('X', 0, 2);
+
+ assertTrue(game.computeWinner().isPresent());
+ assertEquals(Symbol.X, game.computeWinner().get());
+ }
+
+ @Test
+ @Disabled("Not implemented")
+ void DeclarePlayerXAsAWinnerIfThreeDiagonallyRightDown() {
+ game.play('X', 0, 0);
+ game.play('O', 1, 0);
+ game.play('X', 1, 1);
+ game.play('O', 2, 1);
+ game.play('X', 2, 2);
+
+ System.out.println(game.printBoard());
+ assertTrue(game.computeWinner().isPresent());
+ assertEquals(Symbol.X, game.computeWinner().get());
+ }
+}
diff --git a/java/test/Game_Should.java b/java/test/Game_Should.java
deleted file mode 100644
index 22afb59..0000000
--- a/java/test/Game_Should.java
+++ /dev/null
@@ -1,131 +0,0 @@
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-public class Game_Should {
- private Game game;
-
- @BeforeEach
- public void InitializeGame(){
- game = new Game();
- }
-
- @Test
- public void NotAllowPlayerOToPlayFirst() {
- assertThrows(Exception.class, () -> game.Play('O', 0, 0));
- }
-
- @Test
- public void NotAllowPlayerXToPlayTwiceInARow() {
- assertThrows(Exception.class, () ->{
- game.Play('X', 0, 0);
- game.Play('X', 1, 0);
- });
- }
-
- @Test
- public void NotAllowPlayerToPlayInLastPlayedPosition() {
- assertThrows(Exception.class, () ->{
- game.Play('X', 0, 0);
- game.Play('O', 0, 0);
- });
- }
-
- @Test
- public void NotAllowPlayerToPlayInAnyPlayedPosition() {
- assertThrows(Exception.class, () ->{
- game.Play('X', 0, 0);
- game.Play('O', 1, 0);
- game.Play('X', 0, 0);
- });
- }
-
- @Test
- public void DeclarePlayerXAsAWinnerIfThreeInTopRow() throws Exception
- {
- game.Play('X', 0, 0);
- game.Play('O', 1, 0);
- game.Play('X', 0, 1);
- game.Play('O', 1, 1);
- game.Play('X', 0, 2);
-
- char winner = game.Winner();
-
- assertEquals('X', winner);
- }
-
- @Test
- public void DeclarePlayerOAsAWinnerIfThreeInTopRow() throws Exception
- {
- game.Play('X', 2, 2);
- game.Play('O', 0, 0);
- game.Play('X', 1, 0);
- game.Play('O', 0, 1);
- game.Play('X', 1, 1);
- game.Play('O', 0, 2);
-
- char winner = game.Winner();
-
- assertEquals('O', winner);
- }
-
- @Test
- public void DeclarePlayerXAsAWinnerIfThreeInMiddleRow() throws Exception
- {
- game.Play('X', 1, 0);
- game.Play('O', 0, 0);
- game.Play('X', 1, 1);
- game.Play('O', 0, 1);
- game.Play('X', 1, 2);
-
- char winner = game.Winner();
-
- assertEquals('X', winner);
- }
-
- @Test
- public void DeclarePlayerOAsAWinnerIfThreeInMiddleRow() throws Exception
- {
- game.Play('X', 0, 0);
- game.Play('O', 1, 0);
- game.Play('X', 2, 0);
- game.Play('O', 1, 1);
- game.Play('X', 2, 1);
- game.Play('O', 1, 2);
-
- char winner = game.Winner();
-
- assertEquals('O', winner);
- }
-
- @Test
- public void DeclarePlayerXAsAWinnerIfThreeInBottomRow() throws Exception
- {
- game.Play('X', 2, 0);
- game.Play('O', 0, 0);
- game.Play('X', 2, 1);
- game.Play('O', 0, 1);
- game.Play('X', 2, 2);
-
- char winner = game.Winner();
-
- assertEquals('X', winner);
- }
-
- @Test
- public void DeclarePlayerOAsAWinnerIfThreeInBottomRow() throws Exception
- {
- game.Play('X', 0, 0);
- game.Play('O', 2, 0);
- game.Play('X', 1, 0);
- game.Play('O', 2, 1);
- game.Play('X', 1, 1);
- game.Play('O', 2, 2);
-
- char winner = game.Winner();
-
- assertEquals('O', winner);
- }
-}