Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions android_env/apps/MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Copyright 2025 DeepMind Technologies Limited.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Bazel dependencies for building Catch.
module(
name = "catch",
version = "1.0",
)

bazel_dep(name = "rules_android", version = "0.6.6")
bazel_dep(name = "rules_kotlin", version = "2.1.8")
bazel_dep(name = "rules_jvm_external", version = "6.7")
bazel_dep(name = "rules_robolectric", version = "4.16", repo_name = "robolectric")
bazel_dep(name = "rules_java", version = "9.0.3")
bazel_dep(name = "protobuf", version = "30.0")

# To avoid conflict with different protobuf versions.
single_version_override(
module_name = "protobuf",
version = "30.0",
)

maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

# Need to set testonly = True because the package depends on testonly targets.
maven.artifact(
testonly = True,
artifact = "runner",
group = "androidx.test",
version = "1.7.0",
)
maven.artifact(
testonly = True,
artifact = "junit",
group = "androidx.test.ext",
version = "1.3.0",
)
maven.artifact(
testonly = True,
artifact = "mockito-kotlin",
group = "org.mockito.kotlin",
version = "6.1.0",
)
maven.install(
artifacts = [
"androidx.test.ext:junit:1.3.0",
"androidx.test:runner:1.7.0",
"com.google.guava:guava:32.0.1-jre",
"com.google.truth:truth:1.4.0",
"org.mockito.kotlin:mockito-kotlin:6.1.0",
"org.mockito:mockito-core:5.20.0",
"org.robolectric:robolectric:4.16",
"org.yaml:snakeyaml:2.5",
],
repositories = [
"https://maven.google.com",
"https://repo1.maven.org/maven2",
],
)
use_repo(maven, "maven")

remote_android_extensions = use_extension(
"@rules_android//bzlmod_extensions:android_extensions.bzl",
"remote_android_tools_extensions",
)
use_repo(remote_android_extensions, "android_tools")

android_sdk_repository_extension = use_extension("@rules_android//rules/android_sdk_repository:rule.bzl", "android_sdk_repository_extension")
use_repo(android_sdk_repository_extension, "androidsdk")
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
// Copyright 2025 DeepMind Technologies Limited.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2025 DeepMind Technologies Limited.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.-->



<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.androidenv.accessibilityforwarder">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
// Copyright 2025 DeepMind Technologies Limited.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2025 DeepMind Technologies Limited.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.-->



<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.androidenv.accessibilityforwarder">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
// Copyright 2025 DeepMind Technologies Limited.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2025 DeepMind Technologies Limited.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.-->



<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2025 DeepMind Technologies Limited.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.-->


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.google.androidenv.catch">
<uses-sdk android:minSdkVersion="26"
android:targetSdkVersion="35"/>
<application
android:allowBackup="false"
android:label="@string/app_name"
android:supportsRtl="false"
android:taskAffinity=""
tools:ignore="AllowBackup">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:exported="true"
android:hardwareAccelerated="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
80 changes: 80 additions & 0 deletions android_env/apps/java/com/google/androidenv/catch/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Copyright 2025 DeepMind Technologies Limited.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Classic RL task implemented as an Android app.
load("@rules_android//rules:rules.bzl", "android_binary")
load("@rules_kotlin//kotlin:android.bzl", "kt_android_library")

package(
default_visibility = [":catch_packages"],
)

package_group(
name = "catch_packages",
packages = [
"//java/com/google/androidenv/catch/...",
"//javatests/com/google/androidenv/catch/...",
],
)

licenses(["notice"])

android_binary(
name = "app",
manifest = "AndroidManifest.xml",
multidex = "native",
deps = [":MainActivity"],
)

kt_android_library(
name = "GameLogic",
srcs = ["GameLogic.kt"],
deps = [
"//java/com/google/androidenv/catch/sprite:Background",
"//java/com/google/androidenv/catch/sprite:Ball",
"//java/com/google/androidenv/catch/sprite:LineSegment",
"//java/com/google/androidenv/catch/sprite:Paddle",
],
)

kt_android_library(
name = "GameLogicThread",
srcs = ["GameLogicThread.kt"],
deps = [
":GameLogic",
],
)

kt_android_library(
name = "MainActivity",
srcs = ["MainActivity.kt"],
manifest = "AndroidManifest.xml",
resource_files = glob(["res/**"]),
deps = [
":GameLogic",
":GameLogicThread",
":RenderThread",
"//java/com/google/androidenv/catch/sprite:Background",
"//java/com/google/androidenv/catch/sprite:Ball",
"//java/com/google/androidenv/catch/sprite:Paddle",
],
)

kt_android_library(
name = "RenderThread",
srcs = ["RenderThread.kt"],
deps = [
":GameLogic",
],
)
76 changes: 76 additions & 0 deletions android_env/apps/java/com/google/androidenv/catch/GameLogic.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2025 DeepMind Technologies Limited.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.androidenv.catch

import android.graphics.Canvas
import android.view.MotionEvent
import com.google.androidenv.catch.sprite.Background
import com.google.androidenv.catch.sprite.Ball
import com.google.androidenv.catch.sprite.LineSegment
import com.google.androidenv.catch.sprite.Paddle
import java.time.Duration
import java.time.Instant
import kotlin.random.Random

/** The class that contains the game logic. */
open class GameLogic(
// Expected number of frames per second.
fps: Int = 60,
// Pseudo random number generator.
private val rand: Random = Random.Default,
// Width and height of the game in pixels.
private val width: Int,
private val height: Int,
// UI objects in the game.
private var background: Background = Background(),
private var ball: Ball = Ball(maxX = width, maxY = height, rand = rand),
private var paddle: Paddle = Paddle(maxX = width, y = height),
) {

private val sleepTime: Duration = Duration.ofMillis((1000.0 / fps).toLong())

/** Reinitializes the state of the game. */
// Need to make this open to allow for testing.
open fun reset() {
this.ball.reset()
}

/** Runs one "throw" of a [ball] that needs to be caught by the [paddle]. */
// Need to make this open to allow for testing.
open fun run(): Boolean {
var lastTimestamp = Instant.now()
do {
Thread.sleep(sleepTime.toMillis())
val now = Instant.now()
val interval = Duration.between(lastTimestamp, now)
lastTimestamp = now
ball.update(interval)
} while (!ball.isOutOfBounds())

return ball.intersects(LineSegment(paddle.topLeft(), paddle.topRight()))
}

/** Processes a user event (e.g. a touchscreen event) and updates the [paddle] accordingly. */
fun handleTouch(event: MotionEvent) {
paddle.x = event.x.toInt()
}

/** Renders the game on [c]. */
open fun render(c: Canvas) {
background.draw(c)
ball.draw(c)
paddle.draw(c)
}
}
Loading