Skip to content

Commit

Permalink
Type object pattern #555 (#848)
Browse files Browse the repository at this point in the history
* typeobject pattern

* fixing errors

* fix error cellpool

* Update README.md

* Update README.md
  • Loading branch information
AnaghaSasikumar authored and iluwatar committed Jul 24, 2019
1 parent fedc2d9 commit 0c6237c
Show file tree
Hide file tree
Showing 13 changed files with 877 additions and 0 deletions.
1 change: 1 addition & 0 deletions pom.xml
Expand Up @@ -166,6 +166,7 @@
<module>collection-pipeline</module>
<module>master-worker-pattern</module>
<module>spatial-partition</module>
<module>typeobjectpattern</module>
</modules>

<repositories>
Expand Down
29 changes: 29 additions & 0 deletions typeobjectpattern/README.md
@@ -0,0 +1,29 @@
---
layout: pattern
title: Type-object
folder: typeobjectpattern
permalink: /patterns/typeobjectpattern/
categories: Game Programming Patterns/Behavioral Patterns
tags:
- Java
- Difficulty-Beginner
---

## Intent
As explained in the book Game Programming Patterns by Robert Nystrom, type object pattern helps in

> Allowing flexible creation of new “classes” by creating a single class, each instance of which represents a different type of object
## Applicability
This pattern can be used when:
* We don’t know what types we will need up front.
* We want to be able to modify or add new types without having to recompile or change code.
* Only difference between the different 'types' of objects is the data, not the behaviour.

## Explanation
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.
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.

## Credits
* [Game Programming Patterns/Type Object](http://gameprogrammingpatterns.com/type-object.html) by Robert Nystrom
* [http://www.cs.sjsu.edu/~pearce/modules/patterns/analysis/top.htm]
31 changes: 31 additions & 0 deletions typeobjectpattern/pom.xml
@@ -0,0 +1,31 @@
<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.21.0-SNAPSHOT</version>
</parent>
<artifactId>typeobjectpattern</artifactId>
<dependencies>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</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>
</project>
85 changes: 85 additions & 0 deletions typeobjectpattern/src/main/java/com/iluwatar/typeobject/App.java
@@ -0,0 +1,85 @@
/**
* The MIT License
* Copyright (c) 2014-2016 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 java.io.FileNotFoundException;
import java.io.IOException;
import org.json.simple.parser.ParseException;

/**<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>
*/

public class App {

/**
* Program entry point.
* @param args command line args
*/
public static void main(String[] args) throws FileNotFoundException, IOException, ParseException {
int givenTime = 50; //50ms
int toWin = 500; //points
int pointsWon = 0;
int numOfRows = 3;
long start = System.currentTimeMillis();
long end = System.currentTimeMillis();
int round = 0;
while (pointsWon < toWin && end - start < givenTime) {
round++;
CellPool pool = new CellPool(numOfRows * numOfRows + 5);
CandyGame cg = new CandyGame(numOfRows, pool);
if (round > 1) {
System.out.println("Refreshing..");
} else {
System.out.println("Starting game..");
}
cg.printGameStatus();
end = System.currentTimeMillis();
cg.round((int)(end - start), givenTime);
pointsWon += cg.totalPoints;
end = System.currentTimeMillis();
}
System.out.println("Game Over");
if (pointsWon >= toWin) {
System.out.println(pointsWon);
System.out.println("You win!!");
} else {
System.out.println(pointsWon);
System.out.println("Sorry, you lose!");
}
}
}
60 changes: 60 additions & 0 deletions typeobjectpattern/src/main/java/com/iluwatar/typeobject/Candy.java
@@ -0,0 +1,60 @@
/**
* The MIT License
* Copyright (c) 2014-2016 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;

/**
* The Candy class has a field type, which represents the 'type' of candy. The objects
* are created by parsing the candy.json file.
*/

public class Candy {

enum Type { crushableCandy, rewardFruit };

String name;
Candy parent;
String parentName;
private int points;
private 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;
}

int getPoints() {
return this.points;
}

void setPoints(int a) {
this.points = a;
}

Type getType() {
return this.type;
}
}
171 changes: 171 additions & 0 deletions typeobjectpattern/src/main/java/com/iluwatar/typeobject/CandyGame.java
@@ -0,0 +1,171 @@
/**
* The MIT License
* Copyright (c) 2014-2016 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 java.util.ArrayList;
import com.iluwatar.typeobject.Candy.Type;

/**
* 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.
*/

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 (int i = 0; i < num; i++) {
for (int j = 0; j < num; j++) {
this.cells[i][j] = this.pool.getNewCell();
this.cells[i][j].xIndex = j;
this.cells[i][j].yIndex = i;
}
}
}

static String numOfSpaces(int num) {
String result = "";
for (int i = 0; i < num; i++) {
result += " ";
}
return result;
}

void printGameStatus() {
System.out.println("");
for (int i = 0; i < cells.length; i++) {
for (int j = 0; j < cells.length; j++) {
String candyName = cells[i][j].candy.name;
if (candyName.length() < 20) {
int totalSpaces = 20 - candyName.length();
System.out.print(numOfSpaces(totalSpaces / 2) + cells[i][j].candy.name
+ numOfSpaces(totalSpaces - totalSpaces / 2) + "|");
} else {
System.out.print(candyName + "|");
}
}
System.out.println("");
}
System.out.println("");
}

ArrayList<Cell> adjacentCells(int yIndex, int xIndex) {
ArrayList<Cell> adjacent = new ArrayList<Cell>();
if (yIndex == 0) {
adjacent.add(this.cells[1][xIndex]);
}
if (xIndex == 0) {
adjacent.add(this.cells[yIndex][1]);
}
if (yIndex == cells.length - 1) {
adjacent.add(this.cells[cells.length - 2][xIndex]);
}
if (xIndex == cells.length - 1) {
adjacent.add(this.cells[yIndex][cells.length - 2]);
}
if (yIndex > 0 && yIndex < cells.length - 1) {
adjacent.add(this.cells[yIndex - 1][xIndex]);
adjacent.add(this.cells[yIndex + 1][xIndex]);
}
if (xIndex > 0 && xIndex < cells.length - 1) {
adjacent.add(this.cells[yIndex][xIndex - 1]);
adjacent.add(this.cells[yIndex][xIndex + 1]);
}
return adjacent;
}

boolean continueRound() {
for (int i = 0; i < this.cells.length; i++) {
if (this.cells[cells.length - 1][i].candy.getType().equals(Type.rewardFruit)) {
return true;
}
}
for (int i = 0; i < this.cells.length; i++) {
for (int j = 0; j < this.cells.length; j++) {
if (!this.cells[i][j].candy.getType().equals(Type.rewardFruit)) {
ArrayList<Cell> adj = adjacentCells(i,j);
for (int a = 0; a < adj.size(); a++) {
if (this.cells[i][j].candy.name.equals(adj.get(a).candy.name)) {
return true;
}
}
}
}
}
return false;
}

void handleChange(int points) {
System.out.println("+" + points + " points!");
this.totalPoints += points;
printGameStatus();
}

void round(int timeSoFar, int totalTime) {
long start = System.currentTimeMillis();
long end = System.currentTimeMillis();
while (end - start + timeSoFar < totalTime && continueRound()) {
for (int i = 0; i < this.cells.length; i++) {
int points = 0;
int j = this.cells.length - 1;
while (this.cells[j][i].candy.getType().equals(Type.rewardFruit)) {
points = this.cells[j][i].candy.getPoints();
this.cells[j][i].crush(pool, this.cells);
handleChange(points);
}
}
for (int i = 0; i < this.cells.length; i++) {
int j = cells.length - 1;
int 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 (int i = 0; i < this.cells.length; i++) {
int j = 0;
int points = 0;
while (j < cells.length - 1) {
points = this.cells[i][j].interact(this.cells[i][j + 1], this.pool, this.cells);
if (points != 0) {
handleChange(points);
} else {
j = j + 1;
}
}
}
end = System.currentTimeMillis();
}
}

}

0 comments on commit 0c6237c

Please sign in to comment.