Skip to content
Snippets Groups Projects
Commit 728ab09f authored by jez04's avatar jez04
Browse files

initial

parent 9d204cdd
No related merge requests found
Pipeline #2164 canceled with stages
Showing
with 296 additions and 587 deletions
......@@ -2,49 +2,54 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>vsb-cs-java1</groupId>
<artifactId>lab07v1</artifactId>
<version>0.0.1-SNAPHOST</version>
<groupId>cz.vsb.fei</groupId>
<artifactId>lab08v1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>lab08v1</name>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.release>21</maven.compiler.release>
<JUnit.version>5.11.0</JUnit.version>
<log4j.version>2.23.1</log4j.version>
<lombok.version>1.18.34</lombok.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>${JUnit.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>23</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>23</version>
</dependency>
<!--
https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.11.0</version>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<!--
https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.11.0</version>
<scope>test</scope>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
</dependency>
<!--
https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-params -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.11.0</version>
<scope>test</scope>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency>
......@@ -54,6 +59,7 @@
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
......@@ -61,9 +67,20 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<failOnError>false</failOnError>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.0</version>
</plugin>
</plugins>
</build>
</project>
package cz.vsb.fei.lab;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import lombok.extern.log4j.Log4j2;
/**
* Class <b>App</b> - main class
*
* @author Java I
*/
@Log4j2
public class App {
public static void main(String[] args) throws FileNotFoundException {
log.info("Launching Java application.");
App app = new App();
app.printScores(app.loadScores());
List<Score> scores = app.generateScores(200);
app.saveScores(scores, "test.csv");
app.saveScores(scores, "test/test.csv");
try {
app.printScores(app.loadScoresFromStream(new InputStreamReader(App.class.getResourceAsStream("/bestScore-ok.csv"))));
app.printScores(app.loadScoresFromStream(new InputStreamReader(App.class.getResourceAsStream("/bestScores-err.csv"))));
} catch (ScoreNotLoaded e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public List<Score> generateScores(int count) {
List<Score> scores = new ArrayList<Score>();
for (int i = 0; i < count; i++) {
scores.add(Score.generate());
}
return scores;
}
public void saveScores(List<Score> scores, String file) throws FileNotFoundException {
Paths.get(file).toAbsolutePath().getParent().toFile().mkdirs();
try (PrintWriter printWriter = new PrintWriter(file)) {
for (Score score : scores) {
printWriter.format("%s;%d%n", score.getName(), score.getPoints());
}
}
}
public void printScores(List<Score> scores) {
for (Score score : scores) {
System.out.println(score);
}
}
public List<Score> loadScores() {
try {
List<Score> scores = loadScoresFromFile("bestScores.csv");
return scores;
} catch (ScoreNotLoaded e) {
System.out.println("Exception \"" + e.getMessage() + "\". alreaded loaded scores:" + e.getCount());
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return Collections.emptyList();
}
public List<Score> loadScoresFromFile(String filename) throws ScoreNotLoaded, FileNotFoundException, IOException {
try (Reader inputReader = new FileReader(filename)) {
return loadScoresFromStream(inputReader);
} catch (ScoreNotLoaded e) {
throw new ScoreNotLoaded("Info about scores from file " + filename + "cannot be loaded.", e.getCount(), e);
}
}
public List<Score> loadScoresFromStream(Reader inputReader) throws ScoreNotLoaded {
int count = 0;
try (BufferedReader bufferedReader = new BufferedReader(inputReader)) {
String line;
List<Score> scores = new ArrayList<>();
while ((line = bufferedReader.readLine()) != null) {
String[] parts = line.split(";");
try {
scores.add(new Score(parts[0], Integer.parseInt(parts[1])));
count++;
} catch (NumberFormatException e) {
System.out.println(
String.format("WARNING: Cannot parse points for player %s. Value: %s", parts[0], parts[1]));
}
}
return scores;
} catch (Exception e) {
throw new ScoreNotLoaded("Info about scores cannot be loaded.", count, e);
}
}
}
\ No newline at end of file
package cz.vsb.fei.lab;
public class Score {
private String name;
private int points;
public Score(String name, int points) {
this.name = name;
this.points = points;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPoints() {
return points;
}
public void setPoints(int points) {
this.points = points;
}
public static Score generate() {
return new Score(Utilities.getRandomNick(), Utilities.RANDOM.nextInt(20, 300));
}
@Override
public String toString() {
return "Score [name=" + name + ", points=" + points + "]";
}
}
package cz.vsb.fei.lab;
public class ScoreNotLoaded extends Exception {
private static final long serialVersionUID = -3234631445203246158L;
private int count;
public ScoreNotLoaded(String message, int count) {
super(message);
this.count = count;
}
public ScoreNotLoaded(String message, int count, Throwable cause) {
super(message, cause);
this.count = count;
}
public int getCount() {
return count;
}
}
package cz.vsb.fei.lab;
import java.util.Random;
public class Utilities {
public static final Random RANDOM = new Random();
public static final String[] niks = { "CyberSurfer", "PixelPioneer", "SocialSavvy", "DigitalDynamo", "ByteBuddy", "InstaGuru",
"TikTokTornado", "SnapMaster", "TweetTrendsetter", "ChatChampion", "HashtagHero", "EmojiEnthusiast",
"StoryStylist", "SelfieStar", "FilterFanatic", "VlogVirtuoso", "Memelord", "InfluencerInsider",
"StreamSupreme", "GeekyGizmo", "CodeCommander", "JavaJuggernaut", "ByteNinja", "SyntaxSamurai",
"ClassyCoder", "ObjectOmnipotent", "LoopLegend", "VariableVirtuoso", "DebugDemon", "CompilerCrusader",
"PixelProdigy", "VirtualVoyager", "AlgorithmAce", "DataDynamo", "ExceptionExpert", "BugBuster",
"SyntaxSorcerer", "CodeCrusader", "JavaJester", "NerdyNavigator", "CryptoCaptain", "SocialButterfly",
"AppArchitect", "WebWizard", "FunctionFreak", "PixelArtist", "CyberPhantom", "HackHero", "CacheChampion",
"ScreenSage", "WebWeaver", "LogicLover", "BitBlazer", "NetworkNomad", "ProtocolPioneer", "BinaryBoss",
"StackSultan", "SocialScribe", "RenderRuler", "ScriptSorcerer", "HTMLHero", "PixelProwler", "FrameFreak",
"DataDreamer", "BotBuilder", "ByteBishop", "KeyboardKnight", "DesignDaredevil", "JavaJuggler",
"SyntaxStrategist", "TechTactician", "ProgramProdigy", "BinaryBard", "PixelPoet", "GigabyteGuru",
"TechTrekker", "NetworkNinja", "DataDetective", "MatrixMaster", "CodeConductor", "AppAlchemist",
"ServerSage", "ClusterChampion", "ScriptSensei", "KeyboardKicker", "CacheCrafter", "SocialSpark",
"BinaryBeast", "CodeConnoisseur", "BitBrain", "VirtualVanguard", "SystemSculptor", "RenderRogue",
"CryptoConqueror", "MachineMonarch", "PixelPal", "CompilerCaptain", "BitBuilder", "TechTitan",
"CloudConqueror", "EchoExplorer", "FunctionFanatic", "RobotRanger" };
public static String getRandomNick() {
return niks[RANDOM.nextInt(niks.length)];
}
}
package lab;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
/**
* Class <b>App</b> - extends class Application and it is an entry point of the
* program
*
* @author Java I
*/
public class App extends Application {
static {
System.out.println("aaa");
}
private GameController gameController;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
try {
// Construct a main window with a canvas.
FXMLLoader gameLoader = new FXMLLoader(getClass().getResource("/lab/gameWindow.fxml"));
Parent root = gameLoader.load();
GameController gameController = gameLoader.getController();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.resizableProperty().set(false);
primaryStage.setTitle("Java 1 - 1th laboratory");
primaryStage.show();
// Exit program when main window is closed
primaryStage.setOnCloseRequest(this::exitProgram);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void stop() throws Exception {
gameController.stop();
super.stop();
}
private void exitProgram(WindowEvent evt) {
System.exit(0);
}
}
\ No newline at end of file
package lab;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
public class Bullet extends WorldEntity implements Collisionable{
private static final double SIZE = 20;
protected final Point2D acceleration;
protected Point2D velocity;
public Bullet(World world, Point2D position, Point2D velocity, Point2D acceleration) {
super(world, position);
this.velocity = velocity;
this.acceleration = acceleration;
}
@Override
public void drawInternal(GraphicsContext gc) {
gc.setFill(Color.SILVER);
gc.fillOval(position.getX(), position.getY(), SIZE, SIZE);
}
@Override
public void simulate(double deltaT) {
position = position.add(velocity.multiply(deltaT));
velocity = velocity.add(acceleration.multiply(deltaT));
}
@Override
public Rectangle2D getBoundingBox() {
return new Rectangle2D(position.getX(), position.getY(), SIZE, SIZE);
}
@Override
public boolean intersect(Rectangle2D another) {
return getBoundingBox().intersects(another);
}
@Override
public void hitBy(Collisionable another) {
}
public void setVelocity(Point2D velocity) {
this.velocity = velocity;
}
}
package lab;
import java.util.ArrayList;
import java.util.List;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
public class BulletAnimated extends Bullet {
private static final double SIZE = 40;
private final Point2D initVelocity;
private Cannon cannon;
private Image image = new Image(this.getClass().getResourceAsStream("fireball-transparent.gif"));
private List<HitListener> hitListeners =
new ArrayList<>();
public BulletAnimated(World world, Cannon cannon, Point2D position, Point2D velocity, Point2D acceleration) {
super(world, position, velocity, acceleration);
this.initVelocity = velocity;
this.cannon = cannon;
}
@Override
public void drawInternal(GraphicsContext gc) {
gc.drawImage(image, getPosition().getX(), getPosition().getY(), SIZE, SIZE);
gc.strokeRect(position.getX(), position.getY(), SIZE, SIZE);
}
@Override
public void hitBy(Collisionable another) {
if (another instanceof Ufo) {
world.remove(this);
fireUfoDestroyed();
}
}
public void reload() {
position = cannon.getPosition();
velocity = new Point2D(0, 0);
}
public boolean addHitListener(HitListener e) {
return hitListeners.add(e);
}
public boolean removeHitListener(HitListener o) {
return hitListeners.remove(o);
}
private void fireUfoDestroyed() {
for (HitListener hitListener : hitListeners) {
hitListener.ufoDestroyed();
}
}
}
\ No newline at end of file
package lab;
import javafx.geometry.Point2D;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Transform;
public class Cannon extends WorldEntity{
private static final double LENGTH = 60;
private static final double WIDTH = 15;
private double angle;
private double angleDelta = -25;
public Cannon(World world, Point2D position, double angle) {
super(world, position);
this.angle = angle;
}
@Override
public void drawInternal(GraphicsContext gc) {
gc.transform(new Affine(Transform.rotate(angle, position.getX(), position.getY() + WIDTH / 2)));
gc.setFill(Color.BROWN);
gc.fillRect(position.getX(), position.getY(), LENGTH, WIDTH);
}
@Override
public void simulate(double deltaT) {
// angle += angleDelta * deltaT;
// if (angle >= 0 || angle <= -90) {
// angleDelta = -angleDelta;
// }
}
public double getAngle() {
return angle;
}
public void setAngle(double angle) {
this.angle = -angle;
}
}
package lab;
import javafx.geometry.Rectangle2D;
public interface Collisionable {
Rectangle2D getBoundingBox();
boolean intersect(Rectangle2D another);
void hitBy(Collisionable another);
}
package lab;
import javafx.scene.canvas.GraphicsContext;
public interface DrawableSimulable {
void draw(GraphicsContext gc);
void simulate(double deltaT);
}
package lab;
import javafx.animation.AnimationTimer;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
public class DrawingThread extends AnimationTimer {
private final Canvas canvas;
private final GraphicsContext gc;
private final World world;
private long lastTime;
public DrawingThread(Canvas canvas) {
this.canvas = canvas;
this.gc = canvas.getGraphicsContext2D();
world = new World(canvas.getWidth(), canvas.getHeight());
lastTime = System.nanoTime();
}
/**
* Draws objects into the canvas. Put you code here.
*/
@Override
public void handle(long now) {
double deltaT = (now - lastTime) / 1e9;
// call draw on world
this.world.draw(gc);
// call simulate on world
this.world.simulate(deltaT);
lastTime = now;
}
public World getWorld() {
return world;
}
}
package lab;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.geometry.Point2D;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
public class GameController {
@FXML
private Slider angle;
@FXML
private Slider speed;
@FXML
private Canvas canvas;
private DrawingThread timer;
@FXML
private Label hits;
private int hitcount = 0;
@FXML
void fire(ActionEvent event) {
double angle = timer.getWorld().getCannon().getAngle();
double angleRad = Math.toRadians(angle);
double speedValue = speed.getValue();
Point2D velocity = new Point2D(
Math.cos(angleRad)*speedValue,
Math.sin(angleRad)*speedValue);
BulletAnimated bulletAnimated = new BulletAnimated(
timer.getWorld(),
timer.getWorld().getCannon(),
timer.getWorld().getCannon().getPosition(),
velocity, World.GRAVITY);
timer.getWorld().add(bulletAnimated);
bulletAnimated.addHitListener(this::increaseHits);
bulletAnimated.addHitListener(
() -> System.out.println("au!!!!"));
}
private void updateHits() {
hits.setText(String.format("Hit count: %03d", hitcount));
}
private void increaseHits() {
hitcount++;
updateHits();
}
@FXML
void initialize() {
assert angle != null : "fx:id=\"angle\" was not injected: check your FXML file 'gameWindow.fxml'.";
assert canvas != null : "fx:id=\"canvas\" was not injected: check your FXML file 'gameWindow.fxml'.";
assert speed != null : "fx:id=\"speed\" was not injected: check your FXML file 'gameWindow.fxml'.";
timer = new DrawingThread(canvas);
timer.start();
angle.valueProperty().addListener(
(observable, oldValue, newValue) ->
timer.getWorld().getCannon().setAngle(newValue.doubleValue()));
}
public void stop() {
timer.stop();
}
}
package lab;
@FunctionalInterface
public interface HitListener {
void ufoDestroyed();
}
package lab;
public class Routines {
public static void sleep(int timeInMilisenonds) {
try {
Thread.sleep(timeInMilisenonds);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static boolean isEndOfThreadRequestedByJavaVM() {
return Thread.interrupted();
}
}
package lab;
import java.util.Random;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
public class Ufo extends WorldEntity implements Collisionable {
private static final Random RANDOM = new Random();
private Image image = new Image(this.getClass().getResourceAsStream("ufo-small.gif"));
private Point2D velocity;
public Ufo(World world) {
this(world, new Point2D(RANDOM.nextDouble(world.getWidth()), RANDOM.nextDouble(0, world.getHeight() * 0.3)),
new Point2D(RANDOM.nextDouble(70, 150), 0));
}
public Ufo(World world, Point2D position, Point2D velocity) {
super(world, position);
this.velocity = velocity;
}
@Override
public void drawInternal(GraphicsContext gc) {
gc.drawImage(image, getPosition().getX(), getPosition().getY());
}
public void changeDirection() {
velocity = velocity.multiply(-1);
}
@Override
public void simulate(double deltaT) {
position = position.add(velocity.multiply(deltaT));
position = new Point2D(position.getX() % world.getWidth(), position.getY());
if(position.getX() < -image.getWidth()) {
position = new Point2D(world.getWidth(), position.getY());
}
}
@Override
public Rectangle2D getBoundingBox() {
return new Rectangle2D(position.getX(), position.getY(), image.getWidth(), image.getHeight());
}
@Override
public boolean intersect(Rectangle2D another) {
return getBoundingBox().intersects(another);
}
@Override
public void hitBy(Collisionable another) {
if(another instanceof BulletAnimated || another instanceof Bullet) {
world.remove(this);
}
}
}
package lab;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javafx.geometry.Point2D;
import javafx.scene.canvas.GraphicsContext;
public class World {
public static final Point2D GRAVITY = new Point2D(0, 9.81);
private final double width;
private final double height;
private List<DrawableSimulable> entities;
private Collection<DrawableSimulable> entitiesToRemove = new LinkedList<DrawableSimulable>();
private Collection<DrawableSimulable> entitiesToAdd = new LinkedList<DrawableSimulable>();
private Cannon cannon;
// private BulletAnimated bulletAnimated;
public World(double width, double height) {
this.width = width;
this.height = height;
entities = new ArrayList<>();
cannon = new Cannon(this, new Point2D(0, height - 20), -45);
// bulletAnimated = new BulletAnimated(this, cannon, new Point2D(0, height), new Point2D(50, -80),
// GRAVITY);
entities.add(cannon);
entities.add(new Bullet(this, new Point2D(0, height), new Point2D(30, -30), new Point2D(0, 9.81)));
// entities.add(bulletAnimated);
for (int i = 0; i < 3; i++) {
entities.add(new Ufo(this));
}
}
public void draw(GraphicsContext gc) {
gc.clearRect(0, 0, width, height);
gc.save();
for (DrawableSimulable entity : entities) {
entity.draw(gc);
}
gc.restore();
}
public void simulate(double deltaT) {
for (DrawableSimulable entity : entities) {
entity.simulate(deltaT);
}
for (int i = 0; i < entities.size(); i++) {
if (entities.get(i) instanceof Collisionable c1) {
for (int j = i + 1; j < entities.size(); j++) {
if (entities.get(j) instanceof Collisionable c2) {
if (c1.intersect(c2.getBoundingBox())) {
c1.hitBy(c2);
c2.hitBy(c1);
}
}
}
}
}
entities.removeAll(entitiesToRemove);
entities.addAll(entitiesToAdd);
entitiesToAdd.clear();
entitiesToRemove.clear();
}
public double getWidth() {
return width;
}
public void add(DrawableSimulable entity) {
entitiesToAdd.add(entity);
}
public void remove(DrawableSimulable entity) {
entitiesToRemove.add(entity);
}
public double getHeight() {
return height;
}
public Cannon getCannon() {
return cannon;
}
// public BulletAnimated getBulletAnimated() {
// return bulletAnimated;
// }
//
}
\ No newline at end of file
package lab;
import javafx.geometry.Point2D;
import javafx.scene.canvas.GraphicsContext;
public abstract class WorldEntity implements DrawableSimulable{
protected final World world;
protected Point2D position;
public WorldEntity(World world, Point2D position) {
this.world = world;
this.position = position;
}
@Override
public final void draw(GraphicsContext gc) {
gc.save();
drawInternal(gc);
gc.restore();
}
public abstract void drawInternal(GraphicsContext gc);
public Point2D getPosition() {
return position;
}
}
module lab01 {
requires transitive javafx.controls;
requires javafx.fxml;
requires javafx.base;
opens lab to javafx.fxml;
exports lab;
module cz.vsb.fei.lab08v1 {
requires static lombok;
requires org.apache.logging.log4j;
opens cz.vsb.fei.lab;
exports cz.vsb.fei.lab;
}
\ No newline at end of file
BitBlazer;292
CodeConductor;118
NerdyNavigator;206
SelfieStar;113
VlogVirtuoso;199
KeyboardKicker;255
CompilerCaptain;84
KeyboardKnight;176
CodeCommander;160
ProtocolPioneer;157
PixelPoet;45
SyntaxSamurai;67
AppArchitect;288
ByteNinja;104
PixelArtist;112
RenderRuler;153
FunctionFreak;291
BitBuilder;77
DataDetective;276
SocialButterfly;210
WebWeaver;276
FilterFanatic;162
GigabyteGuru;172
StoryStylist;225
StoryStylist;37
WebWeaver;62
StreamSupreme;197
SocialScribe;36
ProgramProdigy;135
InstaGuru;235
JavaJester;93
VariableVirtuoso;120
MatrixMaster;292
SyntaxStrategist;230
DesignDaredevil;163
SocialButterfly;231
TechTactician;121
AppArchitect;131
ScriptSensei;171
ClassyCoder;115
CacheCrafter;79
EchoExplorer;75
StackSultan;209
SocialSavvy;274
BitBuilder;167
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment