From dbe0ff41fd50148c73fcbe2e9df54105b74c4b0a Mon Sep 17 00:00:00 2001 From: Gavrilov Date: Thu, 21 Mar 2019 17:28:27 +0300 Subject: [PATCH 01/19] map generator implemented --- .../example/danil/throughthemaze/map/Map.java | 241 ++++++++++++++++++ .../danil/throughthemaze/map/Segment.java | 33 +++ .../danil/throughthemaze/map/Vertex.java | 36 +++ 3 files changed, 310 insertions(+) create mode 100644 app/src/main/java/com/example/danil/throughthemaze/map/Map.java create mode 100644 app/src/main/java/com/example/danil/throughthemaze/map/Segment.java create mode 100644 app/src/main/java/com/example/danil/throughthemaze/map/Vertex.java diff --git a/app/src/main/java/com/example/danil/throughthemaze/map/Map.java b/app/src/main/java/com/example/danil/throughthemaze/map/Map.java new file mode 100644 index 0000000..1394f87 --- /dev/null +++ b/app/src/main/java/com/example/danil/throughthemaze/map/Map.java @@ -0,0 +1,241 @@ +package com.example.danil.throughthemaze.map; + +import java.util.*; + +public class Map { + public static final double VERTEX_RADIUS = 1; + public static final double CORRIDOR_WIDTH = VERTEX_RADIUS / 2; + public static final double BORDER = 100; + private int size; + private Vertex[] vertexes; + private ArrayList> edges; + + public Map(int size) { + this.size = size; + vertexes = new Vertex[size]; + edges = new ArrayList<>(size); + } + + public void generate() { + while (true) { + generateVertexes(); + ArrayList indexes = new ArrayList<>(); + for (int i = 0; i < size; i++) { + indexes.add(i); + edges.get(i).clear(); + } + if (triangulate(indexes) && check(indexes)) { + break; + } + } + } + + private void generateVertexes() { + while (true) { + for (int i = 0; i < size; i++) { + vertexes[i] = new Vertex(Math.random() * BORDER, Math.random() * BORDER); + } + boolean correct = true; + for (int i = 0; i < size; i++) { + for (int j = i + 1; j < size; j++) { + if (vertexes[i].dist(vertexes[j]) < 2 * VERTEX_RADIUS) { + correct = false; + } + } + } + if (correct) { + break; + } + } + } + + private boolean intersects(Vertex a, Vertex b, Vertex c) { + Segment p = new Segment(a, b); + Segment q = new Segment(a, c); + Segment r = new Segment(b, c); + if (p.dist(c) < VERTEX_RADIUS + CORRIDOR_WIDTH) { + return true; + } + if (q.dist(b) < VERTEX_RADIUS + CORRIDOR_WIDTH) { + return true; + } + if (r.dist(a) < VERTEX_RADIUS + CORRIDOR_WIDTH) { + return true; + } + return false; + } + + private boolean intersects(Vertex a, Vertex b, Vertex c, Vertex d) { + Segment p = new Segment(a, b); + Segment q = new Segment(c, d); + int sum = 0; + sum += p.side(c); + sum += p.side(d); + if (sum != 1) { + return false; + } + sum += q.side(a); + sum += q.side(b); + return (sum == 2); + } + + private boolean check(ArrayList indexes) { + for (int i: indexes) { + for (int j: edges.get(i)) { + for (int k: indexes) { + if (i != k && j != k && intersects(vertexes[i], vertexes[j], vertexes[k])) { + return false; + } + for (int l: edges.get(k)) { + if (i != k && i != l && j != k && j != l + && intersects(vertexes[i], vertexes[j], vertexes[k], vertexes[l])) { + return false; + } + } + } + } + } + return true; + } + + private boolean oneSide(Vertex a, Vertex b, ArrayList indexes) { + Segment x = new Segment(a, b); + int side = 0; + for (int i: indexes) { + side += x.side(vertexes[i]); + } + return (side == 0 || side == indexes.size() - 2); + } + + private void removeIntersections(Vertex a, Vertex b, ArrayList indexes) { + for (int i: indexes) { + LinkedList newEdges = new LinkedList<>(); + for (int j: edges.get(i)) { + if (!intersects(a, b, vertexes[i], vertexes[j])) { + newEdges.add(j); + } + } + edges.set(i, newEdges); + } + } + + private boolean triangulate(ArrayList indexes) { + if (indexes.size() <= 4) { + for (int i = 0; i < indexes.size(); i++) { + for (int j = i + 1; j < indexes.size(); j++) { + int a = indexes.get(i); + int b = indexes.get(j); + edges.get(a).add(b); + edges.get(b).add(a); + } + } + if (check(indexes)) { + return true; + } + if (indexes.size() == 3) { + return false; + } + for (int i = 1; i < 4; i++) { + int j = 2, k = 3; + if (i == 2) { + j = 1; + } + if (i == 3) { + k = 1; + } + Vertex a = vertexes[indexes.get(0)]; + Vertex b = vertexes[indexes.get(i)]; + Vertex c = vertexes[indexes.get(j)]; + Vertex d = vertexes[indexes.get(k)]; + if (!intersects(a, b, c, d)) { + continue; + } + edges.get(0).remove((Integer) i); + edges.get(i).remove((Integer) 0); + if (check(indexes)) { + return true; + } + edges.get(0).add(i); + edges.get(i).add(0); + edges.get(j).remove((Integer) k); + edges.get(k).remove((Integer) j); + return check(indexes); + } + } + ArrayList list = new ArrayList<>(); + for (int i: indexes) { + list.add(vertexes[i]); + } + Collections.sort(list, Vertex.compareX); + int mid = size / 2; + if (size < 12 && size != 8) { + mid = 3; + } + double division = (list.get(mid).x + list.get(mid + 1).x) / 2; + ArrayList left = new ArrayList<>(); + ArrayList right = new ArrayList<>(); + for (int i: indexes) { + if (vertexes[i].x < division) { + left.add(i); + } else { + right.add(i); + } + } + if (!triangulate(left) || !triangulate(right)) { + return false; + } + int startLeft = -1; + int endLeft = -1; + int startRight = -1; + int endRight = -1; + for (int i: left) { + for (int j: right) { + if (oneSide(vertexes[i], vertexes[j], indexes)) { + if (startLeft == -1) { + startLeft = i; + startRight = j; + } else { + endLeft = i; + endRight = j; + } + } + } + } + Set unmarked = new HashSet<>(); + for (int i: indexes) { + if (i != startLeft && i != startRight) { + unmarked.add(i); + } + } + while (startLeft != endLeft || startRight != endRight) { + Vertex center = vertexes[startLeft].midTo(vertexes[startRight]); + int closest = -1; + for (int i: unmarked) { + if (closest == -1 || center.dist(vertexes[i]) < center.dist(vertexes[closest])) { + closest = i; + } + } + unmarked.remove(closest); + if (vertexes[closest].x < division) { + removeIntersections(vertexes[startRight], vertexes[closest], left); + edges.get(startRight).add(closest); + edges.get(closest).add(startRight); + if (!edges.get(startLeft).contains(closest)) { + edges.get(startLeft).add(closest); + edges.get(closest).add(startLeft); + } + startLeft = closest; + } else { + removeIntersections(vertexes[startLeft], vertexes[closest], right); + edges.get(startLeft).add(closest); + edges.get(closest).add(startLeft); + if (!edges.get(startRight).contains(closest)) { + edges.get(startRight).add(closest); + edges.get(closest).add(startRight); + } + startRight = closest; + } + } + return true; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/danil/throughthemaze/map/Segment.java b/app/src/main/java/com/example/danil/throughthemaze/map/Segment.java new file mode 100644 index 0000000..0da5142 --- /dev/null +++ b/app/src/main/java/com/example/danil/throughthemaze/map/Segment.java @@ -0,0 +1,33 @@ +package com.example.danil.throughthemaze.map; + +public class Segment { + private static final double EPS = 1e-8; + public Vertex a; + public Vertex b; + + Segment(Vertex a, Vertex b) { + this.a = a; + this.b = b; + } + + public double dist(Vertex c) { + double scalar1 = (c.x - a.x) * (b.x - a.x) + (c.y - a.y) * (b.y - a.y); + double scalar2 = (b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y); + if (scalar1 < EPS) { + return a.dist(c); + } + if (scalar2 < scalar1 + EPS) { + return b.dist(c); + } + double coefficient = scalar1 / scalar2; + return c.dist(new Vertex(a.x + coefficient * (c.x - a.x), a.y + coefficient * (c.y - a.y))); + } + + public int side(Vertex c) { + double vector = (c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x); + if (vector > EPS) { + return 1; + } + return 0; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/danil/throughthemaze/map/Vertex.java b/app/src/main/java/com/example/danil/throughthemaze/map/Vertex.java new file mode 100644 index 0000000..f11d25e --- /dev/null +++ b/app/src/main/java/com/example/danil/throughthemaze/map/Vertex.java @@ -0,0 +1,36 @@ +package com.example.danil.throughthemaze.map; + +import java.util.ArrayList; +import java.util.Comparator; + +public class Vertex { + public static Comparator compareX = new Comparator() { + @Override + public int compare(Vertex o1, Vertex o2) { + if (o1.x < o2.x) { + return 1; + } + if (o1.x > o2.x) { + return -1; + } + return 0; + } + }; + + public double x; + public double y; + public ArrayList adjacentVertexes = new ArrayList<>(); + + public Vertex(double x, double y) { + this.x = x; + this.y = y; + } + + public double dist(Vertex other) { + return Math.sqrt((x - other.x) * (x - other.x) + (y - other.y) * (y - other.y)); + } + + public Vertex midTo(Vertex other) { + return new Vertex((x + other.x) / 2, (y + other.y) / 2); + } +} \ No newline at end of file From 6d7085b1e90d899e9581d6b25ee4eadf3fce8a82 Mon Sep 17 00:00:00 2001 From: Gavrilov Date: Fri, 22 Mar 2019 22:42:24 +0300 Subject: [PATCH 02/19] map generation algorithm fixed, maps are now saved in database after generation, added class to install database on android --- .idea/misc.xml | 2 +- app/build.gradle | 1 + app/src/main/assets/databases/maps.sqlite3 | Bin 0 -> 86016 bytes .../throughthemaze/database/MapDBHandler.java | 72 ++++++++++++++++++ .../throughthemaze/database/MapDBManager.java | 66 ++++++++++++++++ .../example/danil/throughthemaze/map/Map.java | 41 +++++----- .../throughthemaze/map/MapGenerator.java | 18 +++++ .../danil/throughthemaze/map/Segment.java | 2 +- .../danil/throughthemaze/map/Vertex.java | 8 +- 9 files changed, 183 insertions(+), 27 deletions(-) create mode 100644 app/src/main/assets/databases/maps.sqlite3 create mode 100644 app/src/main/java/com/example/danil/throughthemaze/database/MapDBHandler.java create mode 100644 app/src/main/java/com/example/danil/throughthemaze/database/MapDBManager.java create mode 100644 app/src/main/java/com/example/danil/throughthemaze/map/MapGenerator.java diff --git a/.idea/misc.xml b/.idea/misc.xml index 7bfef59..892046b 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/app/build.gradle b/app/build.gradle index 3ed0b6f..7b22278 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,6 +19,7 @@ android { } dependencies { + api group:'org.xerial', name:'sqlite-jdbc', version:'3.23.1' implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' diff --git a/app/src/main/assets/databases/maps.sqlite3 b/app/src/main/assets/databases/maps.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..0f651e3ece355ebd0ac8a46fbc429f54bebddf7a GIT binary patch literal 86016 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 0) { + out.write(buf, 0, len); + } + } + } catch (IOException e) { + throw new RuntimeException("The $DATABASE_NAME couldn't be installed", e); + } + } + + public MapDBHandler(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + this.context = context; + preferences = context.getSharedPreferences( + context.getPackageName() + ".database_versions", Context.MODE_PRIVATE); + } + + @Override + public void onCreate(SQLiteDatabase db) {} + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {} + + @Override + public SQLiteDatabase getWritableDatabase() { + throw new RuntimeException("The $DATABASE_NAME database is not writable"); + } + + @Override + public SQLiteDatabase getReadableDatabase() { + installOrUpdateIfNecessary(); + return super.getReadableDatabase(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/danil/throughthemaze/database/MapDBManager.java b/app/src/main/java/com/example/danil/throughthemaze/database/MapDBManager.java new file mode 100644 index 0000000..f243968 --- /dev/null +++ b/app/src/main/java/com/example/danil/throughthemaze/database/MapDBManager.java @@ -0,0 +1,66 @@ +package com.example.danil.throughthemaze.database; + +import android.database.sqlite.SQLiteDatabase; +import com.example.danil.throughthemaze.map.Map; +import com.example.danil.throughthemaze.map.Vertex; +import org.sqlite.JDBC; + +import java.sql.*; +import java.util.ArrayList; +import java.util.List; + +public class MapDBManager { + + private SQLiteDatabase database; + private static Connection connection; + private static final String CON_STR = "jdbc:sqlite:app/src/main/assets/databases/maps.sqlite3"; + + public MapDBManager(SQLiteDatabase database) { + this.database = database; + } + + public static void createOrClear() throws SQLException { + DriverManager.registerDriver(new JDBC()); + connection = DriverManager.getConnection(CON_STR); + ResultSet res = connection.getMetaData().getTables( + null, null, "%", null); + List names = new ArrayList<>(); + while (res.next()) { + names.add(res.getString(3)); + } + res.close(); + for (String name: names) { + try (Statement statement = connection.createStatement()) { + statement.execute("DROP TABLE " + name); + } + } + } + + public static void addMap(Map map, int id) throws SQLException { + try (Statement statement = connection.createStatement()) { + statement.execute("CREATE TABLE Vertexes" + id + "(ID INTEGER, x FLOAT, y FLOAT)"); + } + for (int i = 0; i < map.size; i++) { + try (PreparedStatement statement = connection.prepareStatement( + "INSERT INTO Vertexes" + id + " VALUES (?, ?, ?)")) { + statement.setInt(1, i); + statement.setDouble(2, map.vertexes[i].x); + statement.setDouble(3, map.vertexes[i].y); + statement.execute(); + } + } + try (Statement statement = connection.createStatement()) { + statement.execute("CREATE TABLE Edges" + id + "(v INTEGER, u INTEGER)"); + } + for (int i = 0; i < map.size; i++) { + for (int j: map.edges.get(i)) { + try (PreparedStatement statement = connection.prepareStatement( + "INSERT INTO Edges" + id + " VALUES (?, ?)")) { + statement.setInt(1, i); + statement.setInt(2, j); + statement.execute(); + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/danil/throughthemaze/map/Map.java b/app/src/main/java/com/example/danil/throughthemaze/map/Map.java index 1394f87..d1e27f4 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/map/Map.java +++ b/app/src/main/java/com/example/danil/throughthemaze/map/Map.java @@ -3,17 +3,26 @@ import java.util.*; public class Map { - public static final double VERTEX_RADIUS = 1; - public static final double CORRIDOR_WIDTH = VERTEX_RADIUS / 2; - public static final double BORDER = 100; - private int size; - private Vertex[] vertexes; - private ArrayList> edges; + private static final double VERTEX_RADIUS = 1; + private static final double CORRIDOR_WIDTH = VERTEX_RADIUS / 2; + private static final double BORDER = 1000; + public int size; + public Vertex[] vertexes; + public ArrayList> edges; public Map(int size) { this.size = size; vertexes = new Vertex[size]; - edges = new ArrayList<>(size); + edges = new ArrayList<>(); + for (int i = 0; i < size; i++) { + edges.add(new LinkedList()); + } + } + + public Map(int size, Vertex[] vertexes, ArrayList> edges) { + this.size = size; + this.vertexes = vertexes; + this.edges = edges; } public void generate() { @@ -51,17 +60,9 @@ private void generateVertexes() { private boolean intersects(Vertex a, Vertex b, Vertex c) { Segment p = new Segment(a, b); - Segment q = new Segment(a, c); - Segment r = new Segment(b, c); if (p.dist(c) < VERTEX_RADIUS + CORRIDOR_WIDTH) { return true; } - if (q.dist(b) < VERTEX_RADIUS + CORRIDOR_WIDTH) { - return true; - } - if (r.dist(a) < VERTEX_RADIUS + CORRIDOR_WIDTH) { - return true; - } return false; } @@ -161,17 +162,18 @@ private boolean triangulate(ArrayList indexes) { edges.get(k).remove((Integer) j); return check(indexes); } + return check(indexes); } ArrayList list = new ArrayList<>(); for (int i: indexes) { list.add(vertexes[i]); } Collections.sort(list, Vertex.compareX); - int mid = size / 2; - if (size < 12 && size != 8) { + int mid = list.size() / 2; + if (list.size() < 12 && list.size() != 8) { mid = 3; } - double division = (list.get(mid).x + list.get(mid + 1).x) / 2; + double division = (list.get(mid).x + list.get(mid - 1).x) / 2; ArrayList left = new ArrayList<>(); ArrayList right = new ArrayList<>(); for (int i: indexes) { @@ -215,6 +217,9 @@ private boolean triangulate(ArrayList indexes) { closest = i; } } + if (closest == -1) { + return false; + } unmarked.remove(closest); if (vertexes[closest].x < division) { removeIntersections(vertexes[startRight], vertexes[closest], left); diff --git a/app/src/main/java/com/example/danil/throughthemaze/map/MapGenerator.java b/app/src/main/java/com/example/danil/throughthemaze/map/MapGenerator.java new file mode 100644 index 0000000..1eb91c7 --- /dev/null +++ b/app/src/main/java/com/example/danil/throughthemaze/map/MapGenerator.java @@ -0,0 +1,18 @@ +package com.example.danil.throughthemaze.map; + +import com.example.danil.throughthemaze.database.MapDBManager; + +import java.sql.SQLException; + +public class MapGenerator { + public static void main(String[] args) throws SQLException { + MapDBManager.createOrClear(); + int numberOfMaps = Integer.parseInt(args[0]); + int size = Integer.parseInt(args[1]); + for (int i = 0; i < numberOfMaps; i++) { + Map map = new Map(size); + map.generate(); + MapDBManager.addMap(map, i); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/danil/throughthemaze/map/Segment.java b/app/src/main/java/com/example/danil/throughthemaze/map/Segment.java index 0da5142..b431fe8 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/map/Segment.java +++ b/app/src/main/java/com/example/danil/throughthemaze/map/Segment.java @@ -20,7 +20,7 @@ public double dist(Vertex c) { return b.dist(c); } double coefficient = scalar1 / scalar2; - return c.dist(new Vertex(a.x + coefficient * (c.x - a.x), a.y + coefficient * (c.y - a.y))); + return c.dist(new Vertex(a.x + coefficient * (b.x - a.x), a.y + coefficient * (b.y - a.y))); } public int side(Vertex c) { diff --git a/app/src/main/java/com/example/danil/throughthemaze/map/Vertex.java b/app/src/main/java/com/example/danil/throughthemaze/map/Vertex.java index f11d25e..391e379 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/map/Vertex.java +++ b/app/src/main/java/com/example/danil/throughthemaze/map/Vertex.java @@ -7,13 +7,7 @@ public class Vertex { public static Comparator compareX = new Comparator() { @Override public int compare(Vertex o1, Vertex o2) { - if (o1.x < o2.x) { - return 1; - } - if (o1.x > o2.x) { - return -1; - } - return 0; + return Double.compare(o2.x, o1.x); } }; From aba90b564d23bf7f7ba289795a64e16529f2293f Mon Sep 17 00:00:00 2001 From: Gavrilov Date: Fri, 22 Mar 2019 23:14:01 +0300 Subject: [PATCH 03/19] added methods to load map from database --- .../throughthemaze/database/MapDBManager.java | 30 +++++++++++++++++++ .../example/danil/throughthemaze/map/Map.java | 6 ---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/example/danil/throughthemaze/database/MapDBManager.java b/app/src/main/java/com/example/danil/throughthemaze/database/MapDBManager.java index f243968..4e6d288 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/database/MapDBManager.java +++ b/app/src/main/java/com/example/danil/throughthemaze/database/MapDBManager.java @@ -1,5 +1,6 @@ package com.example.danil.throughthemaze.database; +import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import com.example.danil.throughthemaze.map.Map; import com.example.danil.throughthemaze.map.Vertex; @@ -19,6 +20,35 @@ public MapDBManager(SQLiteDatabase database) { this.database = database; } + public Map loadMap(int mapId) { + Map map = null; + try (Cursor cursor = database.rawQuery("SELECT * FROM Vertexes" + mapId, null)) { + map = new Map(cursor.getCount()); + while (cursor.moveToNext()) { + int id = cursor.getInt(1); + double x = cursor.getDouble(2); + double y = cursor.getDouble(3); + map.vertexes[id] = new Vertex(x, y); + } + } + try (Cursor cursor = database.rawQuery("SELECT * FROM Edges" + mapId, null)) { + while (cursor.moveToNext()) { + int i = cursor.getInt(1); + int j = cursor.getInt(2); + map.edges.get(i).add(j); + } + } + return map; + } + + public int getMapCount() { + String query = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE_TABLE'"; + try (Cursor cursor = database.rawQuery(query, null)) { + cursor.moveToFirst(); + return cursor.getInt(1) / 2; + } + } + public static void createOrClear() throws SQLException { DriverManager.registerDriver(new JDBC()); connection = DriverManager.getConnection(CON_STR); diff --git a/app/src/main/java/com/example/danil/throughthemaze/map/Map.java b/app/src/main/java/com/example/danil/throughthemaze/map/Map.java index d1e27f4..eb9422c 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/map/Map.java +++ b/app/src/main/java/com/example/danil/throughthemaze/map/Map.java @@ -19,12 +19,6 @@ public Map(int size) { } } - public Map(int size, Vertex[] vertexes, ArrayList> edges) { - this.size = size; - this.vertexes = vertexes; - this.edges = edges; - } - public void generate() { while (true) { generateVertexes(); From 8f73de00c2596edce867cd4ddeb46d7d87fd7d3c Mon Sep 17 00:00:00 2001 From: Gavrilov Date: Sat, 23 Mar 2019 17:27:58 +0300 Subject: [PATCH 04/19] map loader fixed, map drawer added --- app/build.gradle | 3 +- app/src/main/AndroidManifest.xml | 6 ++ .../throughthemaze/GameCycleActivity.java | 41 +++++++++++ .../danil/throughthemaze/MainActivity.java | 7 ++ .../throughthemaze/database/MapDBHandler.java | 6 +- .../throughthemaze/database/MapDBManager.java | 16 ++--- .../example/danil/throughthemaze/map/Map.java | 6 +- .../danil/throughthemaze/map/Vertex.java | 1 + .../danil/throughthemaze/view/Draw2D.java | 70 +++++++++++++++++++ app/src/main/res/layout/activity_main.xml | 3 +- app/src/main/res/values/attrs.xml | 12 ++++ app/src/main/res/values/colors.xml | 2 + app/src/main/res/values/strings.xml | 4 ++ app/src/main/res/values/styles.xml | 12 ++++ 14 files changed, 173 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/com/example/danil/throughthemaze/GameCycleActivity.java create mode 100644 app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java create mode 100644 app/src/main/res/values/attrs.xml diff --git a/app/build.gradle b/app/build.gradle index 7b22278..401527a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,10 +19,11 @@ android { } dependencies { - api group:'org.xerial', name:'sqlite-jdbc', version:'3.23.1' + api group: 'org.xerial', name: 'sqlite-jdbc', version: '3.23.1' implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.android.support:support-v4:28.0.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 15f7658..747aaf2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,6 +16,12 @@ + + \ No newline at end of file diff --git a/app/src/main/java/com/example/danil/throughthemaze/GameCycleActivity.java b/app/src/main/java/com/example/danil/throughthemaze/GameCycleActivity.java new file mode 100644 index 0000000..c93aee1 --- /dev/null +++ b/app/src/main/java/com/example/danil/throughthemaze/GameCycleActivity.java @@ -0,0 +1,41 @@ +package com.example.danil.throughthemaze; + +import android.annotation.SuppressLint; +import android.database.sqlite.SQLiteDatabase; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.os.Handler; +import android.view.MotionEvent; +import android.view.View; +import com.example.danil.throughthemaze.database.MapDBHandler; +import com.example.danil.throughthemaze.database.MapDBManager; +import com.example.danil.throughthemaze.map.Map; +import com.example.danil.throughthemaze.view.Draw2D; + +import java.util.Random; + +public class GameCycleActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + MapDBHandler dbLoader = new MapDBHandler(this); + SQLiteDatabase db = dbLoader.getReadableDatabase(); + MapDBManager manager = new MapDBManager(db); + int mapCount = manager.getMapCount(); + Random random = new Random(); + int mapId = random.nextInt(mapCount); + Map map = manager.loadMap(mapId); + db.close(); + int start = random.nextInt(map.size); + double x = map.vertexes[start].x; + double y = map.vertexes[start].y; + Draw2D draw = new Draw2D(this, map); + draw.x = x; + draw.y = y; + setContentView(draw); + } + +} diff --git a/app/src/main/java/com/example/danil/throughthemaze/MainActivity.java b/app/src/main/java/com/example/danil/throughthemaze/MainActivity.java index 8c5b4d0..c77556c 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/MainActivity.java +++ b/app/src/main/java/com/example/danil/throughthemaze/MainActivity.java @@ -1,7 +1,9 @@ package com.example.danil.throughthemaze; +import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; +import android.view.View; public class MainActivity extends AppCompatActivity { @@ -10,4 +12,9 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } + + public void startSingleplayer(View view) { + Intent intent = new Intent(this, GameCycleActivity.class); + startActivity(intent); + } } diff --git a/app/src/main/java/com/example/danil/throughthemaze/database/MapDBHandler.java b/app/src/main/java/com/example/danil/throughthemaze/database/MapDBHandler.java index d8f5bbb..e3c814f 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/database/MapDBHandler.java +++ b/app/src/main/java/com/example/danil/throughthemaze/database/MapDBHandler.java @@ -32,7 +32,7 @@ private synchronized void installOrUpdateIfNecessary() { } private void installDatabaseFromAssets() { - try (InputStream in = context.getAssets().open("$ASSETS_PATH/$DATABASE_NAME.sqlite3")) { + try (InputStream in = context.getAssets().open(ASSETS_PATH + "/" + DATABASE_NAME + ".sqlite3")) { File output = new File(context.getDatabasePath(DATABASE_NAME).getPath()); try (OutputStream out = new FileOutputStream(output)) { byte[] buf = new byte[1024]; @@ -42,7 +42,7 @@ private void installDatabaseFromAssets() { } } } catch (IOException e) { - throw new RuntimeException("The $DATABASE_NAME couldn't be installed", e); + throw new RuntimeException("The " + DATABASE_NAME + " couldn't be installed", e); } } @@ -61,7 +61,7 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {} @Override public SQLiteDatabase getWritableDatabase() { - throw new RuntimeException("The $DATABASE_NAME database is not writable"); + throw new RuntimeException("The " + DATABASE_NAME + " database is not writable"); } @Override diff --git a/app/src/main/java/com/example/danil/throughthemaze/database/MapDBManager.java b/app/src/main/java/com/example/danil/throughthemaze/database/MapDBManager.java index 4e6d288..11176ea 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/database/MapDBManager.java +++ b/app/src/main/java/com/example/danil/throughthemaze/database/MapDBManager.java @@ -25,16 +25,16 @@ public Map loadMap(int mapId) { try (Cursor cursor = database.rawQuery("SELECT * FROM Vertexes" + mapId, null)) { map = new Map(cursor.getCount()); while (cursor.moveToNext()) { - int id = cursor.getInt(1); - double x = cursor.getDouble(2); - double y = cursor.getDouble(3); + int id = cursor.getInt(0); + double x = cursor.getDouble(1); + double y = cursor.getDouble(2); map.vertexes[id] = new Vertex(x, y); } } try (Cursor cursor = database.rawQuery("SELECT * FROM Edges" + mapId, null)) { while (cursor.moveToNext()) { - int i = cursor.getInt(1); - int j = cursor.getInt(2); + int i = cursor.getInt(0); + int j = cursor.getInt(1); map.edges.get(i).add(j); } } @@ -42,10 +42,10 @@ public Map loadMap(int mapId) { } public int getMapCount() { - String query = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE_TABLE'"; + String query = "SELECT * FROM sqlite_master WHERE type='table'" + + "AND name != 'android_metadata' AND name != 'sqlite_sequence'"; try (Cursor cursor = database.rawQuery(query, null)) { - cursor.moveToFirst(); - return cursor.getInt(1) / 2; + return cursor.getCount() / 2; } } diff --git a/app/src/main/java/com/example/danil/throughthemaze/map/Map.java b/app/src/main/java/com/example/danil/throughthemaze/map/Map.java index eb9422c..07772e8 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/map/Map.java +++ b/app/src/main/java/com/example/danil/throughthemaze/map/Map.java @@ -3,9 +3,9 @@ import java.util.*; public class Map { - private static final double VERTEX_RADIUS = 1; - private static final double CORRIDOR_WIDTH = VERTEX_RADIUS / 2; - private static final double BORDER = 1000; + public static final double VERTEX_RADIUS = 1; + public static final double CORRIDOR_WIDTH = VERTEX_RADIUS / 2; + public static final double BORDER = 1000; public int size; public Vertex[] vertexes; public ArrayList> edges; diff --git a/app/src/main/java/com/example/danil/throughthemaze/map/Vertex.java b/app/src/main/java/com/example/danil/throughthemaze/map/Vertex.java index 391e379..d35582d 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/map/Vertex.java +++ b/app/src/main/java/com/example/danil/throughthemaze/map/Vertex.java @@ -27,4 +27,5 @@ public double dist(Vertex other) { public Vertex midTo(Vertex other) { return new Vertex((x + other.x) / 2, (y + other.y) / 2); } + } \ No newline at end of file diff --git a/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java b/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java new file mode 100644 index 0000000..c19ecc4 --- /dev/null +++ b/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java @@ -0,0 +1,70 @@ +package com.example.danil.throughthemaze.view; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.view.View; +import com.example.danil.throughthemaze.map.Map; +import com.example.danil.throughthemaze.map.Vertex; + +public class Draw2D extends View { + + private static final double SCALE = 20; + + private Map map; + private Paint paint = new Paint(); + public double x; + public double y; + + public Draw2D(Context context, Map map) { + super(context); + this.map = map; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + paint.setStyle(Paint.Style.FILL); + paint.setColor(Color.GREEN); + canvas.drawPaint(paint); + paint.setColor(Color.WHITE); + double width = canvas.getWidth(); + double height = canvas.getHeight(); + width /= 2; + height /= 2; + canvas.save(); + canvas.translate((float)width, (float)height); + canvas.scale((float)SCALE, (float)SCALE); + for (Vertex v: map.vertexes) { + double dx = v.x - x; + double dy = v.y - y; + canvas.drawCircle((float)dx, (float)dy, (float)Map.VERTEX_RADIUS, paint); + } + for (int i = 0; i < map.size; i++) { + for (int j: map.edges.get(i)) { + if (i > j) { + continue; + } + Vertex a = map.vertexes[i]; + Vertex b = map.vertexes[j]; + double dist = a.dist(b); + double ax = a.x - x; + double ay = a.y - y; + double bx = b.x - x; + double by = b.y - y; + double angle = -Math.atan2(by - ay, bx - ax); + canvas.save(); + canvas.rotate((float)Math.toDegrees(angle)); + double newx = ax * Math.cos(angle) - ay * Math.sin(angle); + double newy = ax * Math.sin(angle) + ay * Math.cos(angle) - Map.CORRIDOR_WIDTH; + canvas.drawRect((float)newx, (float)newy, + (float)(newx + dist), (float)(newy + 2 * Map.CORRIDOR_WIDTH), paint); + canvas.restore(); + } + } + paint.setColor(Color.RED); + canvas.drawCircle(0, 0, (float)(Map.CORRIDOR_WIDTH / 2), paint); + canvas.restore(); + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index be431fb..6a5f457 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -14,6 +14,7 @@ android:layout_gravity="center" android:id="@+id/button" tools:text="Singleplayer" android:background="@color/primary_text_material_dark" - android:textColor="@android:color/black" android:text="@string/singleplayer"/> + android:textColor="@android:color/black" android:text="@string/singleplayer" + android:onClick="startSingleplayer"/> \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..be15d56 --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 8a49ce6..38da6ba 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -5,4 +5,6 @@ #FF4081 #f8e540 #f8e552 + + #66000000 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e0ee3fc..e4813bb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,8 @@ Through the maze Singleplayer + + GameCycleActivity + Dummy Button + DUMMY\nCONTENT diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index e5c30ba..089a8b6 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -13,4 +13,16 @@ @color/colorPrimary + + + + From 60484287386a5e0ab70e1fb51efd66b501ca2241 Mon Sep 17 00:00:00 2001 From: Gavrilov Date: Sat, 23 Mar 2019 22:47:45 +0300 Subject: [PATCH 05/19] accelerometer service added --- app/src/main/AndroidManifest.xml | 2 + .../throughthemaze/AccelerometerService.java | 71 ++++++++++++++++++ .../throughthemaze/GameCycleActivity.java | 32 +++++++-- .../danil/throughthemaze/gameplay/Ball.java | 72 +++++++++++++++++++ .../danil/throughthemaze/view/Draw2D.java | 7 +- 5 files changed, 177 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/com/example/danil/throughthemaze/AccelerometerService.java create mode 100644 app/src/main/java/com/example/danil/throughthemaze/gameplay/Ball.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 747aaf2..15606dd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,6 +22,8 @@ android:label="@string/title_activity_game_cycle" android:theme="@style/FullscreenTheme"> + + \ No newline at end of file diff --git a/app/src/main/java/com/example/danil/throughthemaze/AccelerometerService.java b/app/src/main/java/com/example/danil/throughthemaze/AccelerometerService.java new file mode 100644 index 0000000..0b90a58 --- /dev/null +++ b/app/src/main/java/com/example/danil/throughthemaze/AccelerometerService.java @@ -0,0 +1,71 @@ +package com.example.danil.throughthemaze; + +import android.app.Service; +import android.content.Intent; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.IBinder; +import android.support.annotation.Nullable; +import com.example.danil.throughthemaze.gameplay.Ball; + +import java.util.Timer; +import java.util.TimerTask; + +public class AccelerometerService extends Service { + + private SensorManager sensorManager; + private Sensor sensorAcceleration; + private SensorEventListener listener; + private Timer timer; + private Ball ball; + + private double ax; + private double ay; + + @Override + public int onStartCommand(final Intent intent, int flags, int startId) { + super.onStartCommand(intent, flags, startId); + ball = intent.getParcelableExtra(Ball.class.getName()); + sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); + sensorAcceleration = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION); + listener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + ax = event.values[0]; + ay = event.values[1]; + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) {} + }; + sensorManager.registerListener(listener, sensorAcceleration, SensorManager.SENSOR_DELAY_GAME); + + timer = new Timer(); + TimerTask task = new TimerTask() { + @Override + public void run() { + ball.ax = ax; + ball.ay = ay; + intent.putExtra(Ball.class.getName(), ball); + } + }; + timer.schedule(task, 0, 400); + return START_STICKY; + } + + @Override + public void onDestroy() { + super.onDestroy(); + sensorManager.unregisterListener(listener); + timer.cancel(); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + +} diff --git a/app/src/main/java/com/example/danil/throughthemaze/GameCycleActivity.java b/app/src/main/java/com/example/danil/throughthemaze/GameCycleActivity.java index c93aee1..ebba212 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/GameCycleActivity.java +++ b/app/src/main/java/com/example/danil/throughthemaze/GameCycleActivity.java @@ -1,15 +1,12 @@ package com.example.danil.throughthemaze; -import android.annotation.SuppressLint; +import android.content.Intent; import android.database.sqlite.SQLiteDatabase; -import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; -import android.os.Handler; -import android.view.MotionEvent; -import android.view.View; import com.example.danil.throughthemaze.database.MapDBHandler; import com.example.danil.throughthemaze.database.MapDBManager; +import com.example.danil.throughthemaze.gameplay.Ball; import com.example.danil.throughthemaze.map.Map; import com.example.danil.throughthemaze.view.Draw2D; @@ -17,6 +14,10 @@ public class GameCycleActivity extends AppCompatActivity { + private Ball ball; + private Draw2D draw; + private Intent accelerometer; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -30,12 +31,31 @@ protected void onCreate(Bundle savedInstanceState) { Map map = manager.loadMap(mapId); db.close(); int start = random.nextInt(map.size); + int end = -1; + while (end == -1 || end == start) { + end = random.nextInt(map.size); + } double x = map.vertexes[start].x; double y = map.vertexes[start].y; - Draw2D draw = new Draw2D(this, map); + ball = new Ball(x, y); + accelerometer = new Intent(this, AccelerometerService.class); + accelerometer.putExtra(Ball.class.getName(), ball); + startService(accelerometer); + draw = new Draw2D(this, map, end); draw.x = x; draw.y = y; setContentView(draw); } + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + stopService(accelerometer); + } } diff --git a/app/src/main/java/com/example/danil/throughthemaze/gameplay/Ball.java b/app/src/main/java/com/example/danil/throughthemaze/gameplay/Ball.java new file mode 100644 index 0000000..6b801a4 --- /dev/null +++ b/app/src/main/java/com/example/danil/throughthemaze/gameplay/Ball.java @@ -0,0 +1,72 @@ +package com.example.danil.throughthemaze.gameplay; + +import android.graphics.Color; +import android.os.Parcel; +import android.os.Parcelable; + +import java.nio.ByteBuffer; + +public class Ball implements Parcelable { + private static final int SIZE = 52; + public double x; + public double y; + public double vx; + public double vy; + public double ax; + public double ay; + public int color; + + public Ball(double x, double y) { + this.x = x; + this.y = y; + vx = 0; + vy = 0; + ax = 0; + ay = 0; + color = Color.RED; + } + + public Ball(Parcel in) { + byte[] data = new byte[SIZE]; + in.readByteArray(data); + ByteBuffer wrapped = ByteBuffer.wrap(data); + x = wrapped.getDouble(); + y = wrapped.getDouble(); + vx = wrapped.getDouble(); + vy = wrapped.getDouble(); + ax = wrapped.getDouble(); + ay = wrapped.getDouble(); + color = wrapped.getInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + ByteBuffer out = ByteBuffer.allocate(SIZE); + out.putDouble(x); + out.putDouble(y); + out.putDouble(vx); + out.putDouble(vy); + out.putDouble(ax); + out.putDouble(ay); + out.putInt(color); + dest.writeByteArray(out.array()); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public Ball createFromParcel(Parcel source) { + return new Ball(source); + } + + @Override + public Ball[] newArray(int size) { + return new Ball[size]; + } + }; +} \ No newline at end of file diff --git a/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java b/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java index c19ecc4..572a5b6 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java +++ b/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java @@ -14,12 +14,14 @@ public class Draw2D extends View { private Map map; private Paint paint = new Paint(); + private int end; public double x; public double y; - public Draw2D(Context context, Map map) { + public Draw2D(Context context, Map map, int end) { super(context); this.map = map; + this.end = end; } @Override @@ -65,6 +67,9 @@ protected void onDraw(Canvas canvas) { } paint.setColor(Color.RED); canvas.drawCircle(0, 0, (float)(Map.CORRIDOR_WIDTH / 2), paint); + paint.setColor(Color.BLUE); + canvas.drawCircle((float)(map.vertexes[end].x - x), (float)(map.vertexes[end].y - y), + (float)Map.VERTEX_RADIUS, paint); canvas.restore(); } } \ No newline at end of file From 87cf32453ee98ee1a94fccf4a4fdbd03d7480a00 Mon Sep 17 00:00:00 2001 From: Gavrilov Date: Wed, 27 Mar 2019 00:05:54 +0300 Subject: [PATCH 06/19] accelerometer service fixed, screen image is updating now --- app/src/main/AndroidManifest.xml | 4 +- .../throughthemaze/AccelerometerService.java | 4 +- .../throughthemaze/GameCycleActivity.java | 65 ++++++++++++++++++- .../danil/throughthemaze/view/Draw2D.java | 2 + 4 files changed, 71 insertions(+), 4 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 15606dd..4a43c9d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,8 +22,8 @@ android:label="@string/title_activity_game_cycle" android:theme="@style/FullscreenTheme"> - - + + \ No newline at end of file diff --git a/app/src/main/java/com/example/danil/throughthemaze/AccelerometerService.java b/app/src/main/java/com/example/danil/throughthemaze/AccelerometerService.java index 0b90a58..e0aa9b8 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/AccelerometerService.java +++ b/app/src/main/java/com/example/danil/throughthemaze/AccelerometerService.java @@ -48,10 +48,12 @@ public void onAccuracyChanged(Sensor sensor, int accuracy) {} public void run() { ball.ax = ax; ball.ay = ay; + Intent intent = new Intent(Service.INPUT_SERVICE); intent.putExtra(Ball.class.getName(), ball); + sendBroadcast(intent); } }; - timer.schedule(task, 0, 400); + timer.schedule(task, 0, SensorManager.SENSOR_DELAY_GAME); return START_STICKY; } diff --git a/app/src/main/java/com/example/danil/throughthemaze/GameCycleActivity.java b/app/src/main/java/com/example/danil/throughthemaze/GameCycleActivity.java index ebba212..89b5fd8 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/GameCycleActivity.java +++ b/app/src/main/java/com/example/danil/throughthemaze/GameCycleActivity.java @@ -1,6 +1,10 @@ package com.example.danil.throughthemaze; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.database.sqlite.SQLiteDatabase; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; @@ -11,12 +15,15 @@ import com.example.danil.throughthemaze.view.Draw2D; import java.util.Random; +import java.util.concurrent.atomic.AtomicBoolean; public class GameCycleActivity extends AppCompatActivity { - private Ball ball; + private static final long UPDATE_FREQUENCY = 20; + private volatile Ball ball; private Draw2D draw; private Intent accelerometer; + private GameCycleThread cycle; @Override protected void onCreate(Bundle savedInstanceState) { @@ -47,15 +54,71 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(draw); } + private BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + ball = intent.getParcelableExtra(Ball.class.getName()); + } + }; + + class GameCycleThread implements Runnable { + + private Thread worker; + private AtomicBoolean running = new AtomicBoolean(false); + + public void start() { + worker = new Thread(this); + worker.start(); + } + + public void stop() { + running.set(false); + } + + @Override + public void run() { + running.set(true); + while (running.get()) { + long time = System.currentTimeMillis(); + + draw.x = ball.x; + draw.y = ball.y; + draw.invalidate(); + + long cycleTime = System.currentTimeMillis() - time; + if (cycleTime < UPDATE_FREQUENCY) { + try { + Thread.sleep(UPDATE_FREQUENCY - cycleTime); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + }; + @Override protected void onResume() { super.onResume(); + + registerReceiver(receiver, new IntentFilter(Service.INPUT_SERVICE)); + cycle = new GameCycleThread(); + cycle.start(); + } + + @Override + protected void onPause() { + super.onPause(); + + unregisterReceiver(receiver); } @Override protected void onDestroy() { super.onDestroy(); + cycle.stop(); stopService(accelerometer); } + } diff --git a/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java b/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java index 572a5b6..54360af 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java +++ b/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java @@ -1,5 +1,6 @@ package com.example.danil.throughthemaze.view; +import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; @@ -72,4 +73,5 @@ protected void onDraw(Canvas canvas) { (float)Map.VERTEX_RADIUS, paint); canvas.restore(); } + } \ No newline at end of file From c2dad599976d72bf561c2c84f46853265f099c0d Mon Sep 17 00:00:00 2001 From: Gavrilov Date: Sun, 31 Mar 2019 22:51:40 +0300 Subject: [PATCH 07/19] drawer fixed, sensor changed to gravity --- app/src/main/AndroidManifest.xml | 1 + .../danil/throughthemaze/AccelerometerService.java | 2 +- .../example/danil/throughthemaze/view/Draw2D.java | 12 +++++------- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4a43c9d..c6a1dd3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,6 +18,7 @@ diff --git a/app/src/main/java/com/example/danil/throughthemaze/AccelerometerService.java b/app/src/main/java/com/example/danil/throughthemaze/AccelerometerService.java index e0aa9b8..337f23e 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/AccelerometerService.java +++ b/app/src/main/java/com/example/danil/throughthemaze/AccelerometerService.java @@ -29,7 +29,7 @@ public int onStartCommand(final Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); ball = intent.getParcelableExtra(Ball.class.getName()); sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); - sensorAcceleration = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION); + sensorAcceleration = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY); listener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { diff --git a/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java b/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java index 54360af..5453932 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java +++ b/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java @@ -1,6 +1,5 @@ package com.example.danil.throughthemaze.view; -import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; @@ -32,8 +31,8 @@ protected void onDraw(Canvas canvas) { paint.setColor(Color.GREEN); canvas.drawPaint(paint); paint.setColor(Color.WHITE); - double width = canvas.getWidth(); - double height = canvas.getHeight(); + double width = getWidth(); + double height = getHeight(); width /= 2; height /= 2; canvas.save(); @@ -58,11 +57,10 @@ protected void onDraw(Canvas canvas) { double by = b.y - y; double angle = -Math.atan2(by - ay, bx - ax); canvas.save(); + canvas.translate((float)ax, (float)ay); canvas.rotate((float)Math.toDegrees(angle)); - double newx = ax * Math.cos(angle) - ay * Math.sin(angle); - double newy = ax * Math.sin(angle) + ay * Math.cos(angle) - Map.CORRIDOR_WIDTH; - canvas.drawRect((float)newx, (float)newy, - (float)(newx + dist), (float)(newy + 2 * Map.CORRIDOR_WIDTH), paint); + canvas.drawRect(0, (float)(-Map.CORRIDOR_WIDTH), + (float)(dist), (float)(Map.CORRIDOR_WIDTH), paint); canvas.restore(); } } From eab10bfa40de946f46cdea7108f8687e2cbb9afc Mon Sep 17 00:00:00 2001 From: Gavrilov Date: Wed, 3 Apr 2019 07:26:34 +0300 Subject: [PATCH 08/19] basic physics engine added, maps updated, some small bugs fixed --- app/build.gradle | 3 +- .../ExampleInstrumentedTest.java | 2 +- app/src/main/AndroidManifest.xml | 8 +- app/src/main/assets/databases/maps.sqlite3 | Bin 86016 -> 86016 bytes .../throughthemaze/AccelerometerService.java | 7 +- .../throughthemaze/GameCycleActivity.java | 61 ++++++-- .../danil/throughthemaze/gameplay/Ball.java | 9 ++ .../gameplay/PhysicsEngine.java | 139 ++++++++++++++++++ .../example/danil/throughthemaze/map/Map.java | 35 +++-- .../danil/throughthemaze/map/Segment.java | 9 +- .../danil/throughthemaze/map/Vertex.java | 2 - .../danil/throughthemaze/view/Draw2D.java | 2 +- app/src/main/res/layout/activity_main.xml | 23 ++- 13 files changed, 251 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/com/example/danil/throughthemaze/gameplay/PhysicsEngine.java diff --git a/app/build.gradle b/app/build.gradle index 401527a..ef9962b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 28 defaultConfig { - applicationId "com.example.danil.throughthemaze" + applicationId "com.example.danil.com.throughthemaze" minSdkVersion 19 targetSdkVersion 28 versionCode 1 @@ -27,4 +27,5 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + implementation 'org.jetbrains:annotations-java5:15.0' } diff --git a/app/src/androidTest/java/com/example/danil/throughthemaze/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/example/danil/throughthemaze/ExampleInstrumentedTest.java index 4c471c1..7d0bea3 100644 --- a/app/src/androidTest/java/com/example/danil/throughthemaze/ExampleInstrumentedTest.java +++ b/app/src/androidTest/java/com/example/danil/throughthemaze/ExampleInstrumentedTest.java @@ -21,6 +21,6 @@ public void useAppContext() { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); - assertEquals("com.example.danil.throughthemaze", appContext.getPackageName()); + assertEquals("com.example.danil.com.throughthemaze", appContext.getPackageName()); } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c6a1dd3..5924cd3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,7 +9,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> - + @@ -17,13 +17,15 @@ - + + + diff --git a/app/src/main/assets/databases/maps.sqlite3 b/app/src/main/assets/databases/maps.sqlite3 index 0f651e3ece355ebd0ac8a46fbc429f54bebddf7a..a25a33a10df4f081495851ddac705b7217523459 100644 GIT binary patch literal 86016 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 Math.PI) { + angle -= Math.PI; + } + angle *= -2; + double newvx = Math.cos(angle) * ball.vx - Math.sin(angle) * ball.vy; + double newvy = Math.cos(angle) * ball.vy + Math.sin(angle) * ball.vx; + ball.vx = newvx; + ball.vy = newvy; + } + + public synchronized void updateBall(Ball newBall) { + ball = new Ball(newBall); + } + + public synchronized Ball getBall() { + return new Ball(ball); + } + + @org.jetbrains.annotations.Nullable + @Nullable + @Override + public IBinder onBind(Intent intent) { + ball = intent.getParcelableExtra(Ball.class.getName()); + timer = new Timer(); + TimerTask task = new TimerTask() { + @Override + public void run() { + passTime(GameCycleActivity.UPDATE_FREQUENCY / 10); + } + }; + timer.schedule(task, 0, GameCycleActivity.UPDATE_FREQUENCY / 10); + return binder; + } + + @Override + public void onDestroy() { + super.onDestroy(); + timer.cancel(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/danil/throughthemaze/map/Map.java b/app/src/main/java/com/example/danil/throughthemaze/map/Map.java index 07772e8..728a559 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/map/Map.java +++ b/app/src/main/java/com/example/danil/throughthemaze/map/Map.java @@ -5,17 +5,17 @@ public class Map { public static final double VERTEX_RADIUS = 1; public static final double CORRIDOR_WIDTH = VERTEX_RADIUS / 2; - public static final double BORDER = 1000; + public static final double BORDER = 200; public int size; public Vertex[] vertexes; - public ArrayList> edges; + public ArrayList> edges; public Map(int size) { this.size = size; vertexes = new Vertex[size]; edges = new ArrayList<>(); for (int i = 0; i < size; i++) { - edges.add(new LinkedList()); + edges.add(new TreeSet()); } } @@ -104,10 +104,12 @@ private boolean oneSide(Vertex a, Vertex b, ArrayList indexes) { private void removeIntersections(Vertex a, Vertex b, ArrayList indexes) { for (int i: indexes) { - LinkedList newEdges = new LinkedList<>(); + TreeSet newEdges = new TreeSet<>(); for (int j: edges.get(i)) { if (!intersects(a, b, vertexes[i], vertexes[j])) { newEdges.add(j); + } else { + edges.get(j).remove(i); } } edges.set(i, newEdges); @@ -115,6 +117,9 @@ private void removeIntersections(Vertex a, Vertex b, ArrayList indexes) } private boolean triangulate(ArrayList indexes) { + if (indexes.size() <= 2) { + return false; + } if (indexes.size() <= 4) { for (int i = 0; i < indexes.size(); i++) { for (int j = i + 1; j < indexes.size(); j++) { @@ -138,22 +143,22 @@ private boolean triangulate(ArrayList indexes) { if (i == 3) { k = 1; } - Vertex a = vertexes[indexes.get(0)]; - Vertex b = vertexes[indexes.get(i)]; - Vertex c = vertexes[indexes.get(j)]; - Vertex d = vertexes[indexes.get(k)]; - if (!intersects(a, b, c, d)) { + int a = indexes.get(0); + int b = indexes.get(i); + int c = indexes.get(j); + int d = indexes.get(k); + if (!intersects(vertexes[a], vertexes[b], vertexes[c], vertexes[d])) { continue; } - edges.get(0).remove((Integer) i); - edges.get(i).remove((Integer) 0); + edges.get(a).remove(b); + edges.get(b).remove(a); if (check(indexes)) { return true; } - edges.get(0).add(i); - edges.get(i).add(0); - edges.get(j).remove((Integer) k); - edges.get(k).remove((Integer) j); + edges.get(a).add(b); + edges.get(b).add(a); + edges.get(c).remove(d); + edges.get(d).remove(c); return check(indexes); } return check(indexes); diff --git a/app/src/main/java/com/example/danil/throughthemaze/map/Segment.java b/app/src/main/java/com/example/danil/throughthemaze/map/Segment.java index b431fe8..41eaa87 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/map/Segment.java +++ b/app/src/main/java/com/example/danil/throughthemaze/map/Segment.java @@ -5,7 +5,7 @@ public class Segment { public Vertex a; public Vertex b; - Segment(Vertex a, Vertex b) { + public Segment(Vertex a, Vertex b) { this.a = a; this.b = b; } @@ -30,4 +30,11 @@ public int side(Vertex c) { } return 0; } + + public Segment move(double delta) { + Vertex v = new Vertex(a.y - b.y, b.x - a.x); + v.x *= delta / a.dist(b); + v.y *= delta / a.dist(b); + return new Segment(new Vertex(a.x + v.x, a.y + v.y), new Vertex(b.x + v.x, b.y + v.y)); + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/danil/throughthemaze/map/Vertex.java b/app/src/main/java/com/example/danil/throughthemaze/map/Vertex.java index d35582d..e9e82bd 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/map/Vertex.java +++ b/app/src/main/java/com/example/danil/throughthemaze/map/Vertex.java @@ -1,6 +1,5 @@ package com.example.danil.throughthemaze.map; -import java.util.ArrayList; import java.util.Comparator; public class Vertex { @@ -13,7 +12,6 @@ public int compare(Vertex o1, Vertex o2) { public double x; public double y; - public ArrayList adjacentVertexes = new ArrayList<>(); public Vertex(double x, double y) { this.x = x; diff --git a/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java b/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java index 5453932..0f49a43 100644 --- a/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java +++ b/app/src/main/java/com/example/danil/throughthemaze/view/Draw2D.java @@ -55,7 +55,7 @@ protected void onDraw(Canvas canvas) { double ay = a.y - y; double bx = b.x - x; double by = b.y - y; - double angle = -Math.atan2(by - ay, bx - ax); + double angle = Math.atan2(by - ay, bx - ax); canvas.save(); canvas.translate((float)ax, (float)ay); canvas.rotate((float)Math.toDegrees(angle)); diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 6a5f457..199e505 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -8,13 +8,20 @@ android:orientation="vertical" tools:context=".MainActivity"> -