Skip to content

Commit 0c6237c

Browse files
AnaghaSasikumariluwatar
authored andcommitted
Type object pattern iluwatar#555 (iluwatar#848)
* typeobject pattern * fixing errors * fix error cellpool * Update README.md * Update README.md
1 parent fedc2d9 commit 0c6237c

File tree

13 files changed

+877
-0
lines changed

13 files changed

+877
-0
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@
166166
<module>collection-pipeline</module>
167167
<module>master-worker-pattern</module>
168168
<module>spatial-partition</module>
169+
<module>typeobjectpattern</module>
169170
</modules>
170171

171172
<repositories>

typeobjectpattern/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
layout: pattern
3+
title: Type-object
4+
folder: typeobjectpattern
5+
permalink: /patterns/typeobjectpattern/
6+
categories: Game Programming Patterns/Behavioral Patterns
7+
tags:
8+
- Java
9+
- Difficulty-Beginner
10+
---
11+
12+
## Intent
13+
As explained in the book Game Programming Patterns by Robert Nystrom, type object pattern helps in
14+
15+
> Allowing flexible creation of new “classes” by creating a single class, each instance of which represents a different type of object
16+
17+
## Applicability
18+
This pattern can be used when:
19+
* We don’t know what types we will need up front.
20+
* We want to be able to modify or add new types without having to recompile or change code.
21+
* Only difference between the different 'types' of objects is the data, not the behaviour.
22+
23+
## Explanation
24+
Say, we are working on a game which has a hero and many monsters which are going to attack the hero. These monsters have certain attributes like attack, points etc. and come in different 'breeds' like zombie or ogres. The obvious answer is to have a base Monster class which has some fields and methods, which may be overriden by subclasses like the Zombie or Ogre class. But as we continue to build the game, there may be more and more breeds of monsters added and certain attributes may need to be changed in the existing monsters too. The OOP solution of inheriting from the base class would not be an efficient method in this case.
25+
Using the type-object pattern, instead of creating many classes inheriting from a base class, we have 1 class with a field which represents the 'type' of object. This makes the code cleaner and object instantiation also becomes as easy as parsing a json file with the object properties.
26+
27+
## Credits
28+
* [Game Programming Patterns/Type Object](http://gameprogrammingpatterns.com/type-object.html) by Robert Nystrom
29+
* [http://www.cs.sjsu.edu/~pearce/modules/patterns/analysis/top.htm]

typeobjectpattern/pom.xml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<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">
2+
<modelVersion>4.0.0</modelVersion>
3+
<parent>
4+
<groupId>com.iluwatar</groupId>
5+
<artifactId>java-design-patterns</artifactId>
6+
<version>1.21.0-SNAPSHOT</version>
7+
</parent>
8+
<artifactId>typeobjectpattern</artifactId>
9+
<dependencies>
10+
<dependency>
11+
<groupId>com.googlecode.json-simple</groupId>
12+
<artifactId>json-simple</artifactId>
13+
<version>1.1.1</version>
14+
</dependency>
15+
<dependency>
16+
<groupId>org.junit.jupiter</groupId>
17+
<artifactId>junit-jupiter-api</artifactId>
18+
<scope>test</scope>
19+
</dependency>
20+
<dependency>
21+
<groupId>org.junit.jupiter</groupId>
22+
<artifactId>junit-jupiter-engine</artifactId>
23+
<scope>test</scope>
24+
</dependency>
25+
<dependency>
26+
<groupId>org.mockito</groupId>
27+
<artifactId>mockito-core</artifactId>
28+
<scope>test</scope>
29+
</dependency>
30+
</dependencies>
31+
</project>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* The MIT License
3+
* Copyright (c) 2014-2016 Ilkka Seppälä
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*/
23+
24+
package com.iluwatar.typeobject;
25+
26+
import java.io.FileNotFoundException;
27+
import java.io.IOException;
28+
import org.json.simple.parser.ParseException;
29+
30+
/**<p>Type object pattern is the pattern we use when the OOP concept of creating a base class and
31+
* inheriting from it just doesn't work for the case in hand. This happens when we either don't know
32+
* what types we will need upfront, or want to be able to modify or add new types conveniently w/o
33+
* recompiling repeatedly. The pattern provides a solution by allowing flexible creation of required
34+
* objects by creating one class, which has a field which represents the 'type' of the object.</p>
35+
* <p>In this example, we have a mini candy-crush game in action. There are many different candies
36+
* in the game, which may change over time, as we may want to upgrade the game. To make the object
37+
* creation convenient, we have a class {@link Candy} which has a field name, parent, points and
38+
* Type. We have a json file {@link candy} which contains the details about the candies, and this is
39+
* parsed to get all the different candies in {@link JsonParser}. The {@link Cell} class is what the
40+
* game matrix is made of, which has the candies that are to be crushed, and contains information on
41+
* how crushing can be done, how the matrix is to be reconfigured and how points are to be gained.
42+
* The {@link CellPool} class is a pool which reuses the candy cells that have been crushed instead
43+
* of making new ones repeatedly. The {@link CandyGame} class has the rules for the continuation of
44+
* the game and the {@link App} class has the game itself.</p>
45+
*/
46+
47+
public class App {
48+
49+
/**
50+
* Program entry point.
51+
* @param args command line args
52+
*/
53+
public static void main(String[] args) throws FileNotFoundException, IOException, ParseException {
54+
int givenTime = 50; //50ms
55+
int toWin = 500; //points
56+
int pointsWon = 0;
57+
int numOfRows = 3;
58+
long start = System.currentTimeMillis();
59+
long end = System.currentTimeMillis();
60+
int round = 0;
61+
while (pointsWon < toWin && end - start < givenTime) {
62+
round++;
63+
CellPool pool = new CellPool(numOfRows * numOfRows + 5);
64+
CandyGame cg = new CandyGame(numOfRows, pool);
65+
if (round > 1) {
66+
System.out.println("Refreshing..");
67+
} else {
68+
System.out.println("Starting game..");
69+
}
70+
cg.printGameStatus();
71+
end = System.currentTimeMillis();
72+
cg.round((int)(end - start), givenTime);
73+
pointsWon += cg.totalPoints;
74+
end = System.currentTimeMillis();
75+
}
76+
System.out.println("Game Over");
77+
if (pointsWon >= toWin) {
78+
System.out.println(pointsWon);
79+
System.out.println("You win!!");
80+
} else {
81+
System.out.println(pointsWon);
82+
System.out.println("Sorry, you lose!");
83+
}
84+
}
85+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* The MIT License
3+
* Copyright (c) 2014-2016 Ilkka Seppälä
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*/
23+
24+
package com.iluwatar.typeobject;
25+
26+
/**
27+
* The Candy class has a field type, which represents the 'type' of candy. The objects
28+
* are created by parsing the candy.json file.
29+
*/
30+
31+
public class Candy {
32+
33+
enum Type { crushableCandy, rewardFruit };
34+
35+
String name;
36+
Candy parent;
37+
String parentName;
38+
private int points;
39+
private Type type;
40+
41+
Candy(String name, String parentName, Type type, int points) {
42+
this.name = name;
43+
this.parent = null;
44+
this.type = type;
45+
this.points = points;
46+
this.parentName = parentName;
47+
}
48+
49+
int getPoints() {
50+
return this.points;
51+
}
52+
53+
void setPoints(int a) {
54+
this.points = a;
55+
}
56+
57+
Type getType() {
58+
return this.type;
59+
}
60+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/**
2+
* The MIT License
3+
* Copyright (c) 2014-2016 Ilkka Seppälä
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*/
23+
24+
package com.iluwatar.typeobject;
25+
26+
import java.util.ArrayList;
27+
import com.iluwatar.typeobject.Candy.Type;
28+
29+
/**
30+
* The CandyGame class contains the rules for the continuation of the game and has
31+
* the game matrix (field 'cells') and totalPoints gained during the game.
32+
*/
33+
34+
public class CandyGame {
35+
Cell[][] cells;
36+
CellPool pool;
37+
int totalPoints;
38+
39+
CandyGame(int num, CellPool pool) {
40+
this.cells = new Cell[num][num];
41+
this.pool = pool;
42+
this.totalPoints = 0;
43+
for (int i = 0; i < num; i++) {
44+
for (int j = 0; j < num; j++) {
45+
this.cells[i][j] = this.pool.getNewCell();
46+
this.cells[i][j].xIndex = j;
47+
this.cells[i][j].yIndex = i;
48+
}
49+
}
50+
}
51+
52+
static String numOfSpaces(int num) {
53+
String result = "";
54+
for (int i = 0; i < num; i++) {
55+
result += " ";
56+
}
57+
return result;
58+
}
59+
60+
void printGameStatus() {
61+
System.out.println("");
62+
for (int i = 0; i < cells.length; i++) {
63+
for (int j = 0; j < cells.length; j++) {
64+
String candyName = cells[i][j].candy.name;
65+
if (candyName.length() < 20) {
66+
int totalSpaces = 20 - candyName.length();
67+
System.out.print(numOfSpaces(totalSpaces / 2) + cells[i][j].candy.name
68+
+ numOfSpaces(totalSpaces - totalSpaces / 2) + "|");
69+
} else {
70+
System.out.print(candyName + "|");
71+
}
72+
}
73+
System.out.println("");
74+
}
75+
System.out.println("");
76+
}
77+
78+
ArrayList<Cell> adjacentCells(int yIndex, int xIndex) {
79+
ArrayList<Cell> adjacent = new ArrayList<Cell>();
80+
if (yIndex == 0) {
81+
adjacent.add(this.cells[1][xIndex]);
82+
}
83+
if (xIndex == 0) {
84+
adjacent.add(this.cells[yIndex][1]);
85+
}
86+
if (yIndex == cells.length - 1) {
87+
adjacent.add(this.cells[cells.length - 2][xIndex]);
88+
}
89+
if (xIndex == cells.length - 1) {
90+
adjacent.add(this.cells[yIndex][cells.length - 2]);
91+
}
92+
if (yIndex > 0 && yIndex < cells.length - 1) {
93+
adjacent.add(this.cells[yIndex - 1][xIndex]);
94+
adjacent.add(this.cells[yIndex + 1][xIndex]);
95+
}
96+
if (xIndex > 0 && xIndex < cells.length - 1) {
97+
adjacent.add(this.cells[yIndex][xIndex - 1]);
98+
adjacent.add(this.cells[yIndex][xIndex + 1]);
99+
}
100+
return adjacent;
101+
}
102+
103+
boolean continueRound() {
104+
for (int i = 0; i < this.cells.length; i++) {
105+
if (this.cells[cells.length - 1][i].candy.getType().equals(Type.rewardFruit)) {
106+
return true;
107+
}
108+
}
109+
for (int i = 0; i < this.cells.length; i++) {
110+
for (int j = 0; j < this.cells.length; j++) {
111+
if (!this.cells[i][j].candy.getType().equals(Type.rewardFruit)) {
112+
ArrayList<Cell> adj = adjacentCells(i,j);
113+
for (int a = 0; a < adj.size(); a++) {
114+
if (this.cells[i][j].candy.name.equals(adj.get(a).candy.name)) {
115+
return true;
116+
}
117+
}
118+
}
119+
}
120+
}
121+
return false;
122+
}
123+
124+
void handleChange(int points) {
125+
System.out.println("+" + points + " points!");
126+
this.totalPoints += points;
127+
printGameStatus();
128+
}
129+
130+
void round(int timeSoFar, int totalTime) {
131+
long start = System.currentTimeMillis();
132+
long end = System.currentTimeMillis();
133+
while (end - start + timeSoFar < totalTime && continueRound()) {
134+
for (int i = 0; i < this.cells.length; i++) {
135+
int points = 0;
136+
int j = this.cells.length - 1;
137+
while (this.cells[j][i].candy.getType().equals(Type.rewardFruit)) {
138+
points = this.cells[j][i].candy.getPoints();
139+
this.cells[j][i].crush(pool, this.cells);
140+
handleChange(points);
141+
}
142+
}
143+
for (int i = 0; i < this.cells.length; i++) {
144+
int j = cells.length - 1;
145+
int points = 0;
146+
while (j > 0) {
147+
points = this.cells[j][i].interact(this.cells[j - 1][i], this.pool, this.cells);
148+
if (points != 0) {
149+
handleChange(points);
150+
} else {
151+
j = j - 1;
152+
}
153+
}
154+
}
155+
for (int i = 0; i < this.cells.length; i++) {
156+
int j = 0;
157+
int points = 0;
158+
while (j < cells.length - 1) {
159+
points = this.cells[i][j].interact(this.cells[i][j + 1], this.pool, this.cells);
160+
if (points != 0) {
161+
handleChange(points);
162+
} else {
163+
j = j + 1;
164+
}
165+
}
166+
}
167+
end = System.currentTimeMillis();
168+
}
169+
}
170+
171+
}

0 commit comments

Comments
 (0)