Commit unzipped original NetworkedTicTacToeStarter folder
This commit is contained in:
43
Exercises/NetworkedTicTacToe/core/build.gradle
Normal file
43
Exercises/NetworkedTicTacToe/core/build.gradle
Normal file
@@ -0,0 +1,43 @@
|
||||
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
|
||||
eclipse.project.name = appName + '-core'
|
||||
|
||||
dependencies {
|
||||
api "com.badlogicgames.ashley:ashley:$ashleyVersion"
|
||||
api "com.badlogicgames.gdx:gdx-ai:$aiVersion"
|
||||
api "com.badlogicgames.gdx:gdx-box2d:$gdxVersion"
|
||||
api "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
|
||||
api "com.badlogicgames.gdx:gdx:$gdxVersion"
|
||||
api "com.kotcrab.vis:vis-ui:$visUiVersion"
|
||||
api "io.github.libktx:ktx-actors:$ktxVersion"
|
||||
api "io.github.libktx:ktx-ai:$ktxVersion"
|
||||
api "io.github.libktx:ktx-app:$ktxVersion"
|
||||
api "io.github.libktx:ktx-artemis:$ktxVersion"
|
||||
api "io.github.libktx:ktx-ashley:$ktxVersion"
|
||||
api "io.github.libktx:ktx-assets-async:$ktxVersion"
|
||||
api "io.github.libktx:ktx-assets:$ktxVersion"
|
||||
api "io.github.libktx:ktx-async:$ktxVersion"
|
||||
api "io.github.libktx:ktx-box2d:$ktxVersion"
|
||||
api "io.github.libktx:ktx-collections:$ktxVersion"
|
||||
api "io.github.libktx:ktx-freetype-async:$ktxVersion"
|
||||
api "io.github.libktx:ktx-freetype:$ktxVersion"
|
||||
api "io.github.libktx:ktx-graphics:$ktxVersion"
|
||||
api "io.github.libktx:ktx-i18n:$ktxVersion"
|
||||
api "io.github.libktx:ktx-inject:$ktxVersion"
|
||||
api "io.github.libktx:ktx-json:$ktxVersion"
|
||||
api "io.github.libktx:ktx-log:$ktxVersion"
|
||||
api "io.github.libktx:ktx-math:$ktxVersion"
|
||||
api "io.github.libktx:ktx-preferences:$ktxVersion"
|
||||
api "io.github.libktx:ktx-reflect:$ktxVersion"
|
||||
api "io.github.libktx:ktx-scene2d:$ktxVersion"
|
||||
api "io.github.libktx:ktx-style:$ktxVersion"
|
||||
api "io.github.libktx:ktx-tiled:$ktxVersion"
|
||||
api "io.github.libktx:ktx-vis-style:$ktxVersion"
|
||||
api "io.github.libktx:ktx-vis:$ktxVersion"
|
||||
api "net.onedaybeard.artemis:artemis-odb:$artemisOdbVersion"
|
||||
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion"
|
||||
|
||||
if(enableGraalNative == 'true') {
|
||||
implementation "io.github.berstanio:gdx-svmhelper-annotations:$graalHelperVersion"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package u0012604.tictactoe
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.graphics.g2d.Batch
|
||||
import com.badlogic.gdx.graphics.glutils.ShapeRenderer
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.badlogic.gdx.utils.Disposable
|
||||
import com.badlogic.gdx.utils.viewport.Viewport
|
||||
import ktx.assets.disposeSafely
|
||||
import ktx.graphics.use
|
||||
import u0012604.tictactoe.screens.FirstScreen.Companion.NOUGHT_RADIUS
|
||||
|
||||
class Board(val viewport: Viewport) : Disposable {
|
||||
private var thirdOfWidth = 0f
|
||||
private var thirdOfHeight = 0f
|
||||
|
||||
private var halfCellW = 0f
|
||||
private var halfCellH = 0f
|
||||
|
||||
private val shapeRenderer = ShapeRenderer()
|
||||
|
||||
private var boardLines = emptyArray<Pair<Vector2, Vector2>>()
|
||||
|
||||
private val board = arrayOf(
|
||||
arrayOf(Token.EMPTY, Token.EMPTY, Token.EMPTY),
|
||||
arrayOf(Token.EMPTY, Token.EMPTY, Token.EMPTY),
|
||||
arrayOf(Token.EMPTY, Token.EMPTY, Token.EMPTY)
|
||||
)
|
||||
|
||||
fun placeToken(row: Int, col: Int, token: Token) =
|
||||
if(row in (0..2) && col in (0 .. 2) && board[row][col] == Token.EMPTY) {
|
||||
board[row][col] = token
|
||||
true
|
||||
}
|
||||
else {
|
||||
false
|
||||
}
|
||||
|
||||
fun draw(batch: Batch) {
|
||||
shapeRenderer.use(ShapeRenderer.ShapeType.Filled, viewport.camera.combined) { sr ->
|
||||
|
||||
sr.color = Color.RED
|
||||
|
||||
boardLines.forEach { line ->
|
||||
// Gdx.app.log(TAG, "p0:${line.first}, ${line.second}")
|
||||
|
||||
sr.rectLine(line.first, line.second, 10f)
|
||||
}
|
||||
|
||||
board.forEachIndexed { rowIndex, row ->
|
||||
row.forEachIndexed { colIndex, col ->
|
||||
when(col) {
|
||||
Token.NOUGHT -> drawNought(rowIndex, colIndex, sr)
|
||||
|
||||
Token.CROSS -> drawCross(rowIndex, colIndex, sr)
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resize(width: Int, height: Int) {
|
||||
boardLines = emptyArray()
|
||||
|
||||
thirdOfWidth = width / 3f
|
||||
thirdOfHeight = height / 3f
|
||||
|
||||
// Vertical lines
|
||||
val x1 = thirdOfWidth
|
||||
val x2 = Gdx.graphics.width / 1.5f
|
||||
boardLines += Pair(Vector2(x1, 0f), Vector2(x1, height.toFloat()))
|
||||
boardLines += Pair(Vector2(x2, 0f), Vector2(x2, height.toFloat()))
|
||||
|
||||
// Horizontal lines
|
||||
val y1 = thirdOfHeight
|
||||
val v2 = Gdx.graphics.height.toFloat() / 1.5f
|
||||
|
||||
boardLines += Pair(Vector2(0f, y1), Vector2(width.toFloat(), y1))
|
||||
boardLines += Pair(Vector2(0f, v2), Vector2(width.toFloat(), v2))
|
||||
|
||||
halfCellW = x1 / 2f
|
||||
halfCellH = y1 / 2f
|
||||
}
|
||||
|
||||
private fun drawNought(row: Int, col: Int, sr: ShapeRenderer) {
|
||||
val x = col * thirdOfWidth + halfCellW
|
||||
val y = (2 - row) * thirdOfHeight + halfCellH
|
||||
|
||||
sr.circle(x, y, NOUGHT_RADIUS)
|
||||
}
|
||||
|
||||
private fun drawCross(row: Int, col: Int, sr: ShapeRenderer) {
|
||||
val flipRow = 2 - row
|
||||
|
||||
val l1x1 = col * thirdOfWidth + 50f
|
||||
val l1y1 = flipRow * thirdOfHeight + 50f
|
||||
val l1x2 = col * thirdOfWidth - 50f + 2 * halfCellW
|
||||
val l1y2 = flipRow * thirdOfHeight - 50f + 2 * halfCellH
|
||||
|
||||
sr.rectLine(l1x1, l1y1, l1x2, l1y2, 10f)
|
||||
sr.rectLine(l1x1, l1y2, l1x2, l1y1, 10f)
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
shapeRenderer.disposeSafely()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package u0012604.tictactoe
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
interface Serializable {
|
||||
fun serialize() : ByteArray
|
||||
}
|
||||
|
||||
interface Deserializable {
|
||||
fun deserialize(bb: ByteBuffer) : GameMessage
|
||||
}
|
||||
|
||||
enum class GameMessageType(val id: Byte) {
|
||||
JOIN_GAME(1),
|
||||
PLACE_TOKEN(2);
|
||||
|
||||
companion object {
|
||||
fun fromByte(id: Byte) = entries.first { it.id == id }
|
||||
}
|
||||
}
|
||||
|
||||
sealed class GameMessage(val type: GameMessageType) : Serializable {
|
||||
|
||||
override fun serialize() = byteArrayOf(type.id)
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
data class JoinGameMessage(val token: Token) : GameMessage(GameMessageType.JOIN_GAME) {
|
||||
|
||||
override fun serialize(): ByteArray = with(ByteBuffer.allocate(2)) {
|
||||
put(token.type)
|
||||
super.serialize() + array()
|
||||
}
|
||||
|
||||
companion object : Deserializable {
|
||||
|
||||
override fun deserialize(bb: ByteBuffer) = with(bb) {
|
||||
JoinGameMessage(Token.fromByte(get()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
data class PlaceTokenMessage(val row: Int, val col: Int, val token: Token) : GameMessage(GameMessageType.PLACE_TOKEN) {
|
||||
|
||||
override fun serialize(): ByteArray = with(ByteBuffer.allocate(10)) {
|
||||
putInt(row)
|
||||
putInt(col)
|
||||
put(token.type)
|
||||
super.serialize() + array()
|
||||
}
|
||||
|
||||
companion object : Deserializable {
|
||||
override fun deserialize(bb: ByteBuffer) = with(bb) {
|
||||
|
||||
val row = getInt()
|
||||
val col = getInt()
|
||||
val token = Token.fromByte(get())
|
||||
|
||||
PlaceTokenMessage(row, col, token)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package u0012604.tictactoe
|
||||
|
||||
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import ktx.app.KtxGame
|
||||
import ktx.app.KtxScreen
|
||||
import ktx.assets.disposeSafely
|
||||
import ktx.async.KtxAsync
|
||||
import u0012604.tictactoe.networking.NetworkHandler
|
||||
import u0012604.tictactoe.screens.FirstScreen
|
||||
import u0012604.tictactoe.screens.GameOverScreen
|
||||
|
||||
class Main : KtxGame<KtxScreen>() {
|
||||
|
||||
private val clientChannel = Channel<GameMessage>(10)
|
||||
private val serverChannel = Channel<GameMessage>(10)
|
||||
|
||||
private lateinit var networkHandler: NetworkHandler
|
||||
|
||||
override fun create() {
|
||||
KtxAsync.initiate()
|
||||
|
||||
networkHandler = NetworkHandler("A.B.C.D", 4300, serverChannel, clientChannel)
|
||||
|
||||
addScreen(FirstScreen(this, clientChannel, serverChannel))
|
||||
|
||||
addScreen(GameOverScreen())
|
||||
|
||||
setScreen<FirstScreen>()
|
||||
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
super.dispose()
|
||||
serverChannel.close()
|
||||
clientChannel.close()
|
||||
networkHandler.disposeSafely()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package u0012604.tictactoe
|
||||
|
||||
enum class Token(val type: Byte) {
|
||||
EMPTY(0), NOUGHT(1), CROSS(2);
|
||||
|
||||
companion object {
|
||||
fun fromByte(type: Byte) = Token.entries.first { it.type == type }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package u0012604.tictactoe.networking
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.utils.Disposable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
import kotlinx.coroutines.channels.SendChannel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import u0012604.tictactoe.GameMessage
|
||||
import u0012604.tictactoe.GameMessageType
|
||||
import java.net.ConnectException
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Socket
|
||||
import java.net.SocketException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class NetworkHandler(
|
||||
private val host: String,
|
||||
private val port: Int,
|
||||
private val toServerChannel: Channel<GameMessage>, // Messages being received elsewhere in the client to send onwards to the client
|
||||
private val fromServerChannel: SendChannel<GameMessage>, // Messages being sent from the server to elsewhere within client
|
||||
private val connectionTimeout: Int = 3000,
|
||||
private val maxRetries: Int = 10
|
||||
) : Disposable
|
||||
{
|
||||
fun ByteArray.processMessage() = decodeToString().trimEnd{it == Char(0)} // Removes trailing null character
|
||||
|
||||
private val coroutineScope: CoroutineScope =
|
||||
CoroutineScope(SupervisorJob() + Dispatchers.IO).apply {
|
||||
launch {
|
||||
var retries = 0
|
||||
|
||||
while(retries < maxRetries) {
|
||||
if(startNetwork(host, port)) {
|
||||
break;
|
||||
}
|
||||
delay(retries * 500L)
|
||||
retries++
|
||||
}
|
||||
if(retries == maxRetries) {
|
||||
Gdx.app.error(TAG, "Maximum retries ($maxRetries) exceeded, giving up :(")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var socket: Socket? = null
|
||||
|
||||
val isReady: Boolean
|
||||
get() = socket?.isConnected ?: false
|
||||
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private fun startNetwork(
|
||||
host: String,
|
||||
port: Int
|
||||
) = try {
|
||||
var assignedClientId: UShort = 0U
|
||||
|
||||
// Create our socket
|
||||
socket = Socket()
|
||||
|
||||
// Connect with timeout set.
|
||||
socket?.let {
|
||||
val socketAddress = InetSocketAddress(host, port)
|
||||
it.connect(socketAddress, connectionTimeout)
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Coroutine to handle messages
|
||||
// to be sent to the server
|
||||
// -------------------------------------------------------------
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
it.outputStream.apply {
|
||||
while (true) {
|
||||
// 1. Get the next message in the channel
|
||||
// to send onward to the server.
|
||||
val nextMessage = toServerChannel.receive()
|
||||
|
||||
// 2. Write the message to the server
|
||||
// via the socket's output stream
|
||||
write(nextMessage.serialize())
|
||||
|
||||
flush()
|
||||
|
||||
Gdx.app.log(TAG, "Sent the message $nextMessage")
|
||||
|
||||
delay(10L)
|
||||
}
|
||||
}
|
||||
} catch (ex: java.net.SocketException) {
|
||||
Gdx.app.error(TAG, "[SEND] Socket Failed: ${ex.message}")
|
||||
}
|
||||
}
|
||||
// -------------------------------------------------------------
|
||||
// Coroutine to handle messages
|
||||
// being received from the server
|
||||
//
|
||||
// Messages received are then forwarded via the
|
||||
// -------------------------------------------------------------
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
it.inputStream.apply {
|
||||
launch {
|
||||
while (true) {
|
||||
val byteArray = ByteArray(1024)
|
||||
|
||||
// delay(250L)
|
||||
|
||||
// 1. Read data from the socket's
|
||||
// input stream.
|
||||
val count = read(byteArray, 0, 1024)
|
||||
|
||||
if (count == -1) {
|
||||
Gdx.app.error(TAG, "Socket Read Error!")
|
||||
break
|
||||
}
|
||||
|
||||
val gameMessage = buildGameMessage(byteArray)
|
||||
|
||||
fromServerChannel.send(gameMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ex: java.net.SocketException) {
|
||||
Gdx.app.log(TAG, "[RECEIVE] Socket Failure::[${ex.message}")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
true
|
||||
} catch (ex: java.net.SocketTimeoutException) {
|
||||
Gdx.app.error(TAG, "Timeout Exception: ${ex.message}")
|
||||
false
|
||||
} catch (ex: java.net.ConnectException) {
|
||||
Gdx.app.error(TAG, "Connection Exception: ${ex.message}")
|
||||
false
|
||||
} catch (ex: java.net.SocketException) {
|
||||
Gdx.app.error(TAG, "Exception Raised: ${ex.message}")
|
||||
false
|
||||
}
|
||||
|
||||
fun buildGameMessage(ba: ByteArray) = with(ByteBuffer.wrap(ba)) {
|
||||
val messageType = GameMessageType.fromByte(get())
|
||||
|
||||
when(messageType) {
|
||||
GameMessageType.JOIN_GAME -> GameMessage.JoinGameMessage.deserialize(this)
|
||||
|
||||
GameMessageType.PLACE_TOKEN -> GameMessage.PlaceTokenMessage.deserialize(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG = NetworkHandler::class.simpleName!!
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package u0012604.tictactoe.screens
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.InputProcessor
|
||||
import com.badlogic.gdx.graphics.OrthographicCamera
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.badlogic.gdx.utils.viewport.FitViewport
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
import kotlinx.coroutines.channels.SendChannel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import ktx.app.KtxGame
|
||||
import ktx.app.KtxScreen
|
||||
import ktx.app.clearScreen
|
||||
import ktx.assets.disposeSafely
|
||||
import ktx.graphics.use
|
||||
import u0012604.tictactoe.Board
|
||||
import u0012604.tictactoe.GameMessage
|
||||
import u0012604.tictactoe.Token
|
||||
|
||||
class FirstScreen(
|
||||
val game: KtxGame<KtxScreen>,
|
||||
private val receiveChannel: ReceiveChannel<GameMessage>,
|
||||
private val sendChannel: SendChannel<GameMessage>
|
||||
) : KtxScreen, InputProcessor {
|
||||
|
||||
private val batch = SpriteBatch()
|
||||
|
||||
|
||||
private val camera =
|
||||
OrthographicCamera(WIDTH, HEIGHT).apply {
|
||||
position.set(Gdx.graphics.width / 2f, Gdx.graphics.height / 2f, 0f)
|
||||
};
|
||||
private val viewport = FitViewport(Gdx.graphics.width.toFloat(), Gdx.graphics.height.toFloat(), camera)
|
||||
|
||||
private val board = Board(viewport)
|
||||
|
||||
private var touchPosition = Vector2.Zero
|
||||
|
||||
private var localPlayerToken: Token? = null
|
||||
|
||||
private var localPlayerTurn = false
|
||||
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
init {
|
||||
Gdx.input.inputProcessor = this
|
||||
|
||||
initiateIncomingGameMessageHandling()
|
||||
}
|
||||
|
||||
override fun render(delta: Float) {
|
||||
clearScreen(red = 0.7f, green = 0.7f, blue = 0.7f)
|
||||
|
||||
camera.update()
|
||||
|
||||
batch.use { board.draw(it) }
|
||||
}
|
||||
|
||||
override fun resize(width: Int, height: Int) {
|
||||
viewport.update(width, height)
|
||||
|
||||
board.resize(width, height)
|
||||
}
|
||||
|
||||
private fun initiateIncomingGameMessageHandling() {
|
||||
coroutineScope.launch {
|
||||
while(true) {
|
||||
val gm = receiveChannel.receive()
|
||||
|
||||
when(gm) {
|
||||
is GameMessage.JoinGameMessage -> {
|
||||
localPlayerToken = gm.token
|
||||
|
||||
localPlayerTurn = localPlayerToken == Token.NOUGHT
|
||||
}
|
||||
|
||||
is GameMessage.PlaceTokenMessage -> {
|
||||
Gdx.app.log(TAG, "The other player placed a token at: (${gm.row}, ${gm.col})")
|
||||
|
||||
board.placeToken(gm.row, gm.col, gm.token)
|
||||
|
||||
localPlayerTurn = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Gdx.app.log(TAG, "THE MESSAGE RECEIVED IS: $gm")
|
||||
|
||||
delay(10L)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
batch.disposeSafely()
|
||||
board.disposeSafely()
|
||||
}
|
||||
|
||||
override fun keyDown(keycode: Int) = true
|
||||
|
||||
override fun keyUp(keycode: Int) = true
|
||||
|
||||
override fun keyTyped(character: Char) = true
|
||||
|
||||
override fun touchDown(
|
||||
screenX: Int,
|
||||
screenY: Int,
|
||||
pointer: Int,
|
||||
button: Int
|
||||
): Boolean {
|
||||
|
||||
if(!localPlayerTurn)
|
||||
return true;
|
||||
|
||||
val col = ((screenX.toFloat() / Gdx.graphics.width) * 3).toInt()
|
||||
val row = ((screenY.toFloat() / Gdx.graphics.height) * 3).toInt()
|
||||
|
||||
localPlayerToken?.let {
|
||||
if (board.placeToken(row, col, it)) {
|
||||
|
||||
localPlayerTurn = false
|
||||
|
||||
val gameMessage = GameMessage.PlaceTokenMessage(row, col, it)
|
||||
|
||||
coroutineScope.launch {
|
||||
sendChannel.send(gameMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Gdx.app.log(TAG, "THE ROW IS: ($row, $col)")
|
||||
|
||||
touchPosition.set(screenX.toFloat(), screenY.toFloat())
|
||||
|
||||
viewport.unproject(touchPosition)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun touchUp(
|
||||
screenX: Int,
|
||||
screenY: Int,
|
||||
pointer: Int,
|
||||
button: Int
|
||||
) = true
|
||||
|
||||
override fun touchCancelled(
|
||||
screenX: Int,
|
||||
screenY: Int,
|
||||
pointer: Int,
|
||||
button: Int
|
||||
) = true
|
||||
|
||||
override fun touchDragged(
|
||||
screenX: Int,
|
||||
screenY: Int,
|
||||
pointer: Int
|
||||
) = true
|
||||
|
||||
override fun mouseMoved(screenX: Int, screenY: Int) = true
|
||||
|
||||
override fun scrolled(amountX: Float, amountY: Float) = true
|
||||
|
||||
companion object {
|
||||
val TAG = FirstScreen::class.simpleName!!
|
||||
|
||||
const val WIDTH = 100f
|
||||
const val HEIGHT = 16f * WIDTH / 9f
|
||||
|
||||
const val NOUGHT_RADIUS = 100f
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package u0012604.tictactoe.screens
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.BitmapFont
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import ktx.app.KtxScreen
|
||||
import ktx.app.clearScreen
|
||||
import ktx.graphics.use
|
||||
|
||||
class GameOverScreen : KtxScreen {
|
||||
|
||||
private val font = BitmapFont()
|
||||
private val batch = SpriteBatch()
|
||||
|
||||
override fun render(delta: Float) {
|
||||
clearScreen(red = 0.7f, green = 0.7f, blue = 0.7f)
|
||||
|
||||
batch.use {
|
||||
font.draw(it, "Game Over!", 10f, 10f)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user