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
43 changes: 43 additions & 0 deletions android_env/apps/java/com/google/androidenv/MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// 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.

module(
name = "catch",
version = "0.1.0",
)

bazel_dep(name = "rules_kotlin", version = "2.1.8")
bazel_dep(name = "rules_jvm_external", version = "6.8")

maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
maven.install(
artifacts = [
"joda-time:joda-time:2.12.7",
],
repositories = [
"https://repo1.maven.org/maven2",
],
)
use_repo(maven, "maven")

bazel_dep(name = "rules_android", version = "0.6.6")

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")
401 changes: 401 additions & 0 deletions android_env/apps/java/com/google/androidenv/MODULE.bazel.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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"?>
<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>
78 changes: 78 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,78 @@
// 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_applicable_licenses = ["//third_party/py/android_env:license"],
default_visibility = ["//catch:__subpackages__"],
)

licenses(["notice"])

# Build targets will slowly be added to this project and ultimately we'll have
# an android_binary() target that'll contain the app.
kt_android_library(
name = "GameLogic",
srcs = ["GameLogic.kt"],
deps = [
"//catch/sprite:Background",
"//catch/sprite:Ball",
"//catch/sprite:LineSegment",
"//catch/sprite:Paddle",
"@maven//:joda_time_joda_time",
],
)

kt_android_library(
name = "RenderThread",
srcs = ["RenderThread.kt"],
deps = [
":GameLogic",
"@maven//:joda_time_joda_time",
],
)

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

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

kt_android_library(
name = "GameLogicThread",
srcs = ["GameLogicThread.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 kotlin.random.Random
import org.joda.time.Duration
import org.joda.time.Instant

/** 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.millis((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.millis)
val now = Instant.now()
val interval = Duration(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)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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.util.Log

/** A thread that continuously runs the game logic, resetting after each internal [run()]. */
class GameLogicThread(private val game: GameLogic, private val loggingTag: String) : Thread() {

/** Whether this thread should continuously run. */
private var shouldRun: Boolean = true
/** A counter of game runs. */
private var counter: Int = 0

/**
* Lets the current [run()] iteration complete then break exit this [Thread].
*
* Notice that [shouldRun] cannot have a private getter with a public setter (please see
* https://youtrack.jetbrains.com/issue/KT-3110 for details), hence this public function. Also
* notice that we cannot call this function [stop()] since it would shadow [Thread.stop()].
*/
public fun finish() {
shouldRun = false
}

/** Continuously runs the [game] until [finish()] is called. */
public override fun run() {
while (shouldRun) {
game.reset()
Log.i(loggingTag, "${counter++} - ${game.run()}")
}
}
}
Loading