docs: update type object

This commit is contained in:
Ilkka Seppälä
2024-05-22 19:17:57 +03:00
parent e87b18edf3
commit 84df9ad48a
16 changed files with 227 additions and 212 deletions
+222
View File
@@ -0,0 +1,222 @@
---
title: Type Object
category: Creational
language: en
tag:
- Abstraction
- Code simplification
- Data processing
- Game programming
- Extensibility
- Instantiation
- Object composition
- Polymorphism
---
## Also known as
* Type Descriptor
* Type Safe Enumeration
## Intent
Allow creation of flexible and extensible sets of related types.
## Explanation
Real-world example
> An analogous real-world example of the Type Object pattern can be seen in a role-playing game (RPG) character customization system. In such a game, players can choose from various character classes like Warrior, Mage, and Archer, each with its unique set of abilities and attributes. The Type Object pattern allows the game to define these character classes and their behaviors dynamically. Instead of hardcoding the details of each class, the game uses a flexible system where new character types can be added or existing ones modified without changing the underlying game logic. This extensibility lets the developers introduce new character classes through updates or expansions, keeping the game fresh and engaging for players.
In plain words
> The Type Object pattern allows for the creation and management of flexible and extensible sets of related types dynamically, without modifying existing code.
Gameprogrammingpatterns.com says
> Define a type object class and a typed object class. Each type object instance represents a different logical type. Each typed object stores a reference to the type object that describes its type.
**Programmatic example**
The Type Object pattern is a design pattern that allows for the creation of flexible and reusable objects by creating a class with a field that represents the 'type' of the object. This pattern is useful when the types needed are not known upfront, or when there is a need to modify or add new types conveniently without recompiling repeatedly.
In the provided code, the Type Object pattern is implemented in a mini candy-crush game. The game has many different candies, which may change over time as the game is upgraded.
Let's break down the key components of this implementation:
1. **Candy Class**: This class represents the 'type' object in this pattern. Each candy has a name, parent, points, and type. The type is an enum that can be either `CRUSHABLE_CANDY` or `REWARD_FRUIT`.
```java
class Candy {
String name;
Candy parent;
String parentName;
int points;
Type type;
Candy(String name, String parentName, Type type, int points) {
// constructor implementation
}
int getPoints() {
// implementation
}
Type getType() {
// implementation
}
void setPoints(int a) {
// implementation
}
}
```
2. **JsonParser Class**: This class is responsible for parsing the JSON file that contains the details about the candies. It creates a `Candy` object for each candy in the JSON file and stores them in a `Hashtable`.
```java
public class JsonParser {
Hashtable<String, Candy> candies;
JsonParser() {
this.candies = new Hashtable<>();
}
void parse() throws JsonParseException {
// implementation
}
void setParentAndPoints() {
// implementation
}
}
```
3. **Cell Class**: This class represents a cell in the game matrix. Each cell contains a candy that can be crushed. It also contains information on how crushing can be done, how the matrix is to be reconfigured, and how points are to be gained.
```java
class Cell {
Candy candy;
int positionX;
int positionY;
Cell() {
// implementation
}
Cell(Candy candy, int positionX, int positionY) {
// implementation
}
void crush(CellPool pool, Cell[][] cellMatrix) {
// implementation
}
// other methods...
}
```
4. **CandyGame Class**: This class contains the rules for the continuation of the game.
```java
class CandyGame {
Cell[][] cells;
CellPool pool;
int totalPoints;
CandyGame(int num, CellPool pool) {
// implementation
}
boolean continueRound() {
// implementation
}
// other methods...
}
```
5. **CellPool Class**: This class is a pool that reuses the candy cells that have been crushed instead of creating new ones repeatedly.
```java
class CellPool {
int pointer;
List<Cell> pool;
Candy[] randomCode;
CellPool(int num) {
// implementation
}
void addNewCell(Cell c) {
// implementation
}
Candy[] assignRandomCandytypes() {
// implementation
}
Cell getNewCell() {
// implementation
}
}
```
6. **App Class**: This class contains the main method that starts the game.
```java
@Slf4j
public class App {
public static void main(String[] args) {
// implementation
}
}
```
In this implementation, the Type Object pattern allows for the flexible creation of `Candy` objects. The type of each candy is determined at runtime by parsing a JSON file, which makes it easy to add, modify, or remove candy types without having to recompile the code.
## Class diagram
![Type Object](./etc/typeobjectpattern.urm.png "Type Object")
## Applicability
This pattern can be used when:
* Use when you need to create an extensible set of related classes without modifying existing code.
* Ideal for scenarios where types and their behaviors need to be defined at runtime or in a flexible manner.
* Suitable for situations where the number of types is large and may change over time.
* The difference between the different 'types' of objects is the data, not the behaviour.
## Known uses
* Java Collections Framework: Utilizing various collection types like List, Set, and Map.
* Graphics Libraries: Defining different shapes with specific properties and behaviors.
* Game Development: Creating different types of characters or items with unique attributes and behaviors.
## Consequences
Benefits:
* Increases flexibility and extensibility of the code.
* Simplifies the addition of new types without modifying existing code.
* Enhances code readability by organizing related behaviors and properties.
Trade-offs:
* Can increase complexity if not managed properly.
* May lead to performance overhead due to dynamic type checking and handling.
## Related patterns
* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): Often used in conjunction with Type Object to create instances of the types.
* [Strategy](https://java-design-patterns.com/patterns/strategy/): Similar in that it defines a family of algorithms or behaviors, but focuses more on interchangeable behaviors.
* [Prototype](https://java-design-patterns.com/patterns/prototype/): Can be used to create new instances by copying existing ones, supporting dynamic and flexible type creation.
## Credits
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI)
* [Effective Java](https://amzn.to/4cGk2Jz)
* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525)
* [Game Programming Patterns - Type Object](http://gameprogrammingpatterns.com/type-object.html)
* [Types as Objects Pattern - Jon Pearce](http://www.cs.sjsu.edu/~pearce/modules/patterns/analysis/top.htm)
Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

@@ -0,0 +1,72 @@
@startuml
package com.iluwatar.typeobject {
class App {
- LOGGER : Logger {static}
+ App()
+ main(args : String[]) {static}
}
class Candy {
~ name : String
~ parent : Candy
~ parentName : String
- points : int
- type : Type
~ Candy(name : String, parentName : String, type : Type, points : int)
~ getPoints() : int
~ getType() : Type
~ setPoints(a : int)
}
~enum Type {
+ crushableCandy {static}
+ rewardFruit {static}
+ valueOf(name : String) : Type {static}
+ values() : Type[] {static}
}
class CandyGame {
- LOGGER : Logger {static}
~ cells : Cell[][]
~ pool : CellPool
~ totalPoints : int
~ CandyGame(num : int, pool : CellPool)
~ adjacentCells(y : int, x : int) : List<Cell>
~ continueRound() : boolean
~ handleChange(points : int)
~ numOfSpaces(num : int) : String {static}
~ printGameStatus()
~ round(timeSoFar : int, totalTime : int)
}
class Cell {
~ candy : Candy
~ positionX : int
~ positionY : int
~ Cell()
~ Cell(candy : Candy, positionX : int, positionY : int)
~ crush(pool : CellPool, cellMatrix : Cell[][])
~ fillThisSpace(pool : CellPool, cellMatrix : Cell[][])
~ handleCrush(c : Cell, pool : CellPool, cellMatrix : Cell[][])
~ interact(c : Cell, pool : CellPool, cellMatrix : Cell[][]) : int
}
class CellPool {
- RANDOM : Random {static}
~ pointer : int
~ pool : List<Cell>
~ randomCode : Candy[]
~ CellPool(num : int)
~ addNewCell(c : Cell)
~ assignRandomCandytypes() : Candy[]
~ getNewCell() : Cell
}
class JsonParser {
~ candies : Hashtable<String, Candy>
~ JsonParser()
~ parse()
~ setParentAndPoints()
}
}
Cell --> "-candy" Candy
Type ..+ Candy
Candy --> "-type" Type
Candy --> "-parent" Candy
CandyGame --> "-pool" CellPool
CellPool --> "-pool" Cell
@enduml
+71
View File
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
The MIT License
Copyright © 2014-2022 Ilkka Seppälä
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
</parent>
<artifactId>type-object</artifactId>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifest>
<mainClass>com.iluwatar.typeobject.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,87 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.typeobject;
import lombok.extern.slf4j.Slf4j;
/**
* <p>Type object pattern is the pattern we use when the OOP concept of creating a base class and
* inheriting from it just doesn't work for the case in hand. This happens when we either don't know
* what types we will need upfront, or want to be able to modify or add new types conveniently w/o
* recompiling repeatedly. The pattern provides a solution by allowing flexible creation of required
* objects by creating one class, which has a field which represents the 'type' of the object.</p>
* <p>In this example, we have a mini candy-crush game in action. There are many different candies
* in the game, which may change over time, as we may want to upgrade the game. To make the object
* creation convenient, we have a class {@link Candy} which has a field name, parent, points and
* Type. We have a json file {@link candy} which contains the details about the candies, and this is
* parsed to get all the different candies in {@link JsonParser}. The {@link Cell} class is what the
* game matrix is made of, which has the candies that are to be crushed, and contains information on
* how crushing can be done, how the matrix is to be reconfigured and how points are to be gained.
* The {@link CellPool} class is a pool which reuses the candy cells that have been crushed instead
* of making new ones repeatedly. The {@link CandyGame} class has the rules for the continuation of
* the game and the {@link App} class has the game itself.</p>
*/
@Slf4j
public class App {
/**
* Program entry point.
*
* @param args command line args
*/
public static void main(String[] args) {
var givenTime = 50; //50ms
var toWin = 500; //points
var pointsWon = 0;
var numOfRows = 3;
var start = System.currentTimeMillis();
var end = System.currentTimeMillis();
var round = 0;
while (pointsWon < toWin && end - start < givenTime) {
round++;
var pool = new CellPool(numOfRows * numOfRows + 5);
var cg = new CandyGame(numOfRows, pool);
if (round > 1) {
LOGGER.info("Refreshing..");
} else {
LOGGER.info("Starting game..");
}
cg.printGameStatus();
end = System.currentTimeMillis();
cg.round((int) (end - start), givenTime);
pointsWon += cg.totalPoints;
end = System.currentTimeMillis();
}
LOGGER.info("Game Over");
if (pointsWon >= toWin) {
LOGGER.info("" + pointsWon);
LOGGER.info("You win!!");
} else {
LOGGER.info("" + pointsWon);
LOGGER.info("Sorry, you lose!");
}
}
}
@@ -0,0 +1,59 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.typeobject;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
/**
* The Candy class has a field type, which represents the 'type' of candy. The objects are created
* by parsing the candy.json file.
*/
@Getter(AccessLevel.PACKAGE)
public class Candy {
enum Type {
CRUSHABLE_CANDY,
REWARD_FRUIT
}
String name;
Candy parent;
String parentName;
@Setter
private int points;
private final Type type;
Candy(String name, String parentName, Type type, int points) {
this.name = name;
this.parent = null;
this.type = type;
this.points = points;
this.parentName = parentName;
}
}
@@ -0,0 +1,173 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.typeobject;
import com.iluwatar.typeobject.Candy.Type;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
/**
* The CandyGame class contains the rules for the continuation of the game and has the game matrix
* (field 'cells') and totalPoints gained during the game.
*/
@Slf4j
@SuppressWarnings("java:S3776") //"Cognitive Complexity of methods should not be too high"
public class CandyGame {
Cell[][] cells;
CellPool pool;
int totalPoints;
CandyGame(int num, CellPool pool) {
this.cells = new Cell[num][num];
this.pool = pool;
this.totalPoints = 0;
for (var i = 0; i < num; i++) {
for (var j = 0; j < num; j++) {
this.cells[i][j] = this.pool.getNewCell();
this.cells[i][j].positionX = j;
this.cells[i][j].positionY = i;
}
}
}
static String numOfSpaces(int num) {
return " ".repeat(Math.max(0, num));
}
void printGameStatus() {
LOGGER.info("");
for (Cell[] cell : cells) {
for (var j = 0; j < cells.length; j++) {
var candyName = cell[j].candy.name;
if (candyName.length() < 20) {
var totalSpaces = 20 - candyName.length();
LOGGER.info(numOfSpaces(totalSpaces / 2) + cell[j].candy.name
+ numOfSpaces(totalSpaces - totalSpaces / 2) + "|");
} else {
LOGGER.info(candyName + "|");
}
}
LOGGER.info("");
}
LOGGER.info("");
}
List<Cell> adjacentCells(int y, int x) {
var adjacent = new ArrayList<Cell>();
if (y == 0) {
adjacent.add(this.cells[1][x]);
}
if (x == 0) {
adjacent.add(this.cells[y][1]);
}
if (y == cells.length - 1) {
adjacent.add(this.cells[cells.length - 2][x]);
}
if (x == cells.length - 1) {
adjacent.add(this.cells[y][cells.length - 2]);
}
if (y > 0 && y < cells.length - 1) {
adjacent.add(this.cells[y - 1][x]);
adjacent.add(this.cells[y + 1][x]);
}
if (x > 0 && x < cells.length - 1) {
adjacent.add(this.cells[y][x - 1]);
adjacent.add(this.cells[y][x + 1]);
}
return adjacent;
}
boolean continueRound() {
for (var i = 0; i < this.cells.length; i++) {
if (this.cells[cells.length - 1][i].candy.getType().equals(Type.REWARD_FRUIT)) {
return true;
}
}
for (var i = 0; i < this.cells.length; i++) {
for (var j = 0; j < this.cells.length; j++) {
if (!this.cells[i][j].candy.getType().equals(Type.REWARD_FRUIT)) {
var adj = adjacentCells(i, j);
for (Cell cell : adj) {
if (this.cells[i][j].candy.name.equals(cell.candy.name)) {
return true;
}
}
}
}
}
return false;
}
void handleChange(int points) {
LOGGER.info("+" + points + " points!");
this.totalPoints += points;
printGameStatus();
}
void round(int timeSoFar, int totalTime) {
var start = System.currentTimeMillis();
var end = System.currentTimeMillis();
while (end - start + timeSoFar < totalTime && continueRound()) {
for (var i = 0; i < this.cells.length; i++) {
var points = 0;
var j = this.cells.length - 1;
while (this.cells[j][i].candy.getType().equals(Type.REWARD_FRUIT)) {
points = this.cells[j][i].candy.getPoints();
this.cells[j][i].crush(pool, this.cells);
handleChange(points);
}
}
for (var i = 0; i < this.cells.length; i++) {
var j = cells.length - 1;
var points = 0;
while (j > 0) {
points = this.cells[j][i].interact(this.cells[j - 1][i], this.pool, this.cells);
if (points != 0) {
handleChange(points);
} else {
j = j - 1;
}
}
}
for (Cell[] cell : this.cells) {
var j = 0;
var points = 0;
while (j < cells.length - 1) {
points = cell[j].interact(cell[j + 1], this.pool, this.cells);
if (points != 0) {
handleChange(points);
} else {
j = j + 1;
}
}
}
end = System.currentTimeMillis();
}
}
}
@@ -0,0 +1,83 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.typeobject;
import com.iluwatar.typeobject.Candy.Type;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
/**
* The Cell object is what the game matrix is made of and contains the candy which is to be crushed
* or collected as reward.
*/
@AllArgsConstructor
@NoArgsConstructor
public class Cell {
Candy candy;
int positionX;
int positionY;
void crush(CellPool pool, Cell[][] cellMatrix) {
//take out from this position and put back in pool
pool.addNewCell(this);
this.fillThisSpace(pool, cellMatrix);
}
void fillThisSpace(CellPool pool, Cell[][] cellMatrix) {
for (var y = this.positionY; y > 0; y--) {
cellMatrix[y][this.positionX] = cellMatrix[y - 1][this.positionX];
cellMatrix[y][this.positionX].positionY = y;
}
var newC = pool.getNewCell();
cellMatrix[0][this.positionX] = newC;
cellMatrix[0][this.positionX].positionX = this.positionX;
cellMatrix[0][this.positionX].positionY = 0;
}
void handleCrush(Cell c, CellPool pool, Cell[][] cellMatrix) {
if (this.positionY >= c.positionY) {
this.crush(pool, cellMatrix);
c.crush(pool, cellMatrix);
} else {
c.crush(pool, cellMatrix);
this.crush(pool, cellMatrix);
}
}
int interact(Cell c, CellPool pool, Cell[][] cellMatrix) {
if (this.candy.getType().equals(Type.REWARD_FRUIT) || c.candy.getType()
.equals(Type.REWARD_FRUIT)) {
return 0;
} else {
if (this.candy.name.equals(c.candy.name)) {
var pointsWon = this.candy.getPoints() + c.candy.getPoints();
handleCrush(c, pool, cellMatrix);
return pointsWon;
} else {
return 0;
}
}
}
}
@@ -0,0 +1,97 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.typeobject;
import com.google.gson.JsonParseException;
import com.iluwatar.typeobject.Candy.Type;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
/**
* The CellPool class allows the reuse of crushed cells instead of creation of new cells each time.
* The reused cell is given a new candy to hold using the randomCode field which holds all the
* candies available.
*/
@Slf4j
public class CellPool {
private static final SecureRandom RANDOM = new SecureRandom();
public static final String FRUIT = "fruit";
public static final String CANDY = "candy";
List<Cell> pool;
int pointer;
Candy[] randomCode;
CellPool(int num) {
this.pool = new ArrayList<>(num);
try {
this.randomCode = assignRandomCandytypes();
} catch (Exception e) {
LOGGER.error("Error occurred: ", e);
//manually initialising this.randomCode
this.randomCode = new Candy[5];
randomCode[0] = new Candy("cherry", FRUIT, Type.REWARD_FRUIT, 20);
randomCode[1] = new Candy("mango", FRUIT, Type.REWARD_FRUIT, 20);
randomCode[2] = new Candy("purple popsicle", CANDY, Type.CRUSHABLE_CANDY, 10);
randomCode[3] = new Candy("green jellybean", CANDY, Type.CRUSHABLE_CANDY, 10);
randomCode[4] = new Candy("orange gum", CANDY, Type.CRUSHABLE_CANDY, 10);
}
for (int i = 0; i < num; i++) {
var c = new Cell();
c.candy = randomCode[RANDOM.nextInt(randomCode.length)];
this.pool.add(c);
}
this.pointer = num - 1;
}
Cell getNewCell() {
var newCell = this.pool.remove(pointer);
pointer--;
return newCell;
}
void addNewCell(Cell c) {
c.candy = randomCode[RANDOM.nextInt(randomCode.length)]; //changing candytype to new
this.pool.add(c);
pointer++;
}
Candy[] assignRandomCandytypes() throws JsonParseException {
var jp = new JsonParser();
jp.parse();
var randomCode = new Candy[jp.candies.size() - 2]; //exclude generic types 'fruit' and 'candy'
var i = 0;
for (var e = jp.candies.keys(); e.hasMoreElements(); ) {
var s = e.nextElement();
if (!s.equals(FRUIT) && !s.equals(CANDY)) {
//not generic
randomCode[i] = jp.candies.get(s);
i++;
}
}
return randomCode;
}
}
@@ -0,0 +1,80 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.typeobject;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.iluwatar.typeobject.Candy.Type;
import java.io.InputStreamReader;
import java.util.Hashtable;
/**
* The JsonParser class helps parse the json file candy.json to get all the different candies.
*/
public class JsonParser {
Hashtable<String, Candy> candies;
JsonParser() {
this.candies = new Hashtable<>();
}
void parse() throws JsonParseException {
var is = this.getClass().getClassLoader().getResourceAsStream("candy.json");
var reader = new InputStreamReader(is);
var json = (JsonObject) com.google.gson.JsonParser.parseReader(reader);
var array = (JsonArray) json.get("candies");
for (var item : array) {
var candy = (JsonObject) item;
var name = candy.get("name").getAsString();
var parentName = candy.get("parent").getAsString();
var t = candy.get("type").getAsString();
var type = Type.CRUSHABLE_CANDY;
if (t.equals("rewardFruit")) {
type = Type.REWARD_FRUIT;
}
var points = candy.get("points").getAsInt();
var c = new Candy(name, parentName, type, points);
this.candies.put(name, c);
}
setParentAndPoints();
}
void setParentAndPoints() {
for (var e = this.candies.keys(); e.hasMoreElements(); ) {
var c = this.candies.get(e.nextElement());
if (c.parentName == null) {
c.parent = null;
} else {
c.parent = this.candies.get(c.parentName);
}
if (c.getPoints() == 0 && c.parent != null) {
c.setPoints(c.parent.getPoints());
}
}
}
}
+45
View File
@@ -0,0 +1,45 @@
{"candies" : [
{
"name" : "fruit",
"parent" : "null",
"type" : "rewardFruit",
"points" : 20
},
{
"name" : "candy",
"parent" : "null",
"type" : "crushableCandy",
"points" : 10
},
{
"name" : "cherry",
"parent" : "fruit",
"type" : "rewardFruit",
"points" : 0
},
{
"name" : "mango",
"parent" : "fruit",
"type" : "rewardFruit",
"points" : 0
},
{
"name" : "purple popsicle",
"parent" : "candy",
"type" : "crushableCandy",
"points" : 0
},
{
"name" : "green jellybean",
"parent" : "candy",
"type" : "crushableCandy",
"points" : 0
},
{
"name" : "orange gum",
"parent" : "candy",
"type" : "crushableCandy",
"points" : 0
}
]
}
@@ -0,0 +1,70 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.typeobject;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.iluwatar.typeobject.Candy.Type;
import org.junit.jupiter.api.Test;
/**
* The CandyGameTest class tests the methods in the {@link CandyGame} class.
*/
class CandyGameTest {
@Test
void adjacentCellsTest() {
var cg = new CandyGame(3, new CellPool(9));
var arr1 = cg.adjacentCells(0, 0);
var arr2 = cg.adjacentCells(1, 2);
var arr3 = cg.adjacentCells(1, 1);
assertTrue(arr1.size() == 2 && arr2.size() == 3 && arr3.size() == 4);
}
@Test
void continueRoundTest() {
var matrix = new Cell[2][2];
var c1 = new Candy("green jelly", "jelly", Type.CRUSHABLE_CANDY, 5);
var c2 = new Candy("purple jelly", "jelly", Type.CRUSHABLE_CANDY, 5);
var c3 = new Candy("green apple", "apple", Type.REWARD_FRUIT, 10);
matrix[0][0] = new Cell(c1, 0, 0);
matrix[0][1] = new Cell(c2, 1, 0);
matrix[1][0] = new Cell(c3, 0, 1);
matrix[1][1] = new Cell(c2, 1, 1);
var p = new CellPool(4);
var cg = new CandyGame(2, p);
cg.cells = matrix;
var fruitInLastRow = cg.continueRound();
matrix[1][0].crush(p, matrix);
matrix[0][0] = new Cell(c3, 0, 0);
var matchingCandy = cg.continueRound();
matrix[0][1].crush(p, matrix);
matrix[0][1] = new Cell(c3, 1, 0);
var noneLeft = cg.continueRound();
assertTrue(fruitInLastRow && matchingCandy && !noneLeft);
}
}
@@ -0,0 +1,52 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.typeobject;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Hashtable;
import org.junit.jupiter.api.Test;
/**
* The CellPoolTest class tests the methods in the {@link CellPool} class.
*/
class CellPoolTest {
@Test
void assignRandomCandyTypesTest() {
var cp = new CellPool(10);
var ht = new Hashtable<String, Boolean>();
var parentTypes = 0;
for (var i = 0; i < cp.randomCode.length; i++) {
ht.putIfAbsent(cp.randomCode[i].name, true);
if (cp.randomCode[i].name.equals("fruit") || cp.randomCode[i].name.equals("candy")) {
parentTypes++;
}
}
assertTrue(ht.size() == 5 && parentTypes == 0);
}
}
@@ -0,0 +1,63 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.typeobject;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.iluwatar.typeobject.Candy.Type;
import org.junit.jupiter.api.Test;
/**
* The CellTest class tests the methods in the {@link Cell} class.
*/
class CellTest {
@Test
void interactTest() {
var c1 = new Candy("green jelly", "jelly", Type.CRUSHABLE_CANDY, 5);
var c2 = new Candy("green apple", "apple", Type.REWARD_FRUIT, 10);
var matrix = new Cell[4][4];
matrix[0][0] = new Cell(c1, 0, 0);
matrix[0][1] = new Cell(c1, 1, 0);
matrix[0][2] = new Cell(c2, 2, 0);
matrix[0][3] = new Cell(c1, 3, 0);
var cp = new CellPool(5);
var points1 = matrix[0][0].interact(matrix[0][1], cp, matrix);
var points2 = matrix[0][2].interact(matrix[0][3], cp, matrix);
assertTrue(points1 > 0 && points2 == 0);
}
@Test
void crushTest() {
var c1 = new Candy("green jelly", "jelly", Type.CRUSHABLE_CANDY, 5);
var c2 = new Candy("purple candy", "candy", Type.CRUSHABLE_CANDY, 5);
var matrix = new Cell[4][4];
matrix[0][0] = new Cell(c1, 0, 0);
matrix[1][0] = new Cell(c2, 0, 1);
matrix[1][0].crush(new CellPool(5), matrix);
assertEquals("green jelly", matrix[1][0].candy.name);
}
}