Compare commits
16 Commits
| Author | SHA256 | Date | |
|---|---|---|---|
| 9882e1db4c | |||
| 9f5bf0e66a | |||
| 1cc46a94fe | |||
| 4de5a8f88b | |||
| 89ff480c2e | |||
| c009746bc4 | |||
| 99719f5b79 | |||
| fe79c271fa | |||
| ddab82ec7f | |||
| 7974cbd0af | |||
| bdcbfece3b | |||
| 1960fa51a7 | |||
| 75d7e3e101 | |||
| f915f7bbba | |||
| 1136fd018d | |||
| 01da51665e |
1422
Excalidraw/Drawing 2026-03-03 16.04.17.excalidraw.md
Normal file
7932
Excalidraw/class-structure.excalidraw.md
Normal file
2
Excalidraw/class-structure.excalidraw.svg
Normal file
|
After Width: | Height: | Size: 4.6 MiB |
@@ -1,256 +0,0 @@
|
|||||||
#include <SFML/Network.hpp>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstring>
|
|
||||||
#include <iostream>
|
|
||||||
#include <mutex>
|
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
// TODO: move `GameServer` into its own files (h/cpp).
|
|
||||||
// Note: This is compiled with SFML 2.6.2 in mind.
|
|
||||||
// It would work similarly with slightly older versions of SFML.
|
|
||||||
// A thourough rework is necessary for SFML 3.0.
|
|
||||||
|
|
||||||
enum GameMessageType : unsigned char {
|
|
||||||
JOIN_GAME = 0x01, PLACE_TOKEN = 0x02, START_GAME = 0x03, GAME_OVER = 0x04
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Token : unsigned char {
|
|
||||||
NOUGHTS = 0x01, CROSSES = 0x02
|
|
||||||
};
|
|
||||||
|
|
||||||
class GameServer {
|
|
||||||
public:
|
|
||||||
GameServer(unsigned short tcp_port) :
|
|
||||||
m_tcp_port(tcp_port) {}
|
|
||||||
|
|
||||||
|
|
||||||
bool send_start_game_to_clients() {
|
|
||||||
char buf[1] = { START_GAME };
|
|
||||||
std::cout << "Starting the game..." << std::endl;
|
|
||||||
return broadcast_message(buf, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool send_game_over_to_clients() {
|
|
||||||
char buf[1] = { GAME_OVER };
|
|
||||||
std::cout << "Game Over!" << std::endl;
|
|
||||||
return broadcast_message(buf, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Binds to a port and then loops around. For every client that connects,
|
|
||||||
// we start a new thread receiving their messages.
|
|
||||||
void tcp_start()
|
|
||||||
{
|
|
||||||
// BINDING
|
|
||||||
sf::TcpListener listener;
|
|
||||||
sf::Socket::Status status = listener.listen(m_tcp_port, sf::IpAddress("152.105.66.120")); // Make sure to change this!
|
|
||||||
if (status != sf::Socket::Status::Done)
|
|
||||||
{
|
|
||||||
std::cerr << "Error binding listener to port" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "TCP Server is listening to port "
|
|
||||||
<< m_tcp_port
|
|
||||||
<< ", waiting for connections..."
|
|
||||||
<< std::endl;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// ACCEPTING
|
|
||||||
if(m_player_count < 2)
|
|
||||||
{
|
|
||||||
sf::TcpSocket* client = new sf::TcpSocket;
|
|
||||||
status = listener.accept(*client);
|
|
||||||
if (status != sf::Socket::Status::Done)
|
|
||||||
{
|
|
||||||
delete client;
|
|
||||||
} else {
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(m_clients_mutex);
|
|
||||||
m_clients.push_back(client);
|
|
||||||
}
|
|
||||||
std::cout << "New client connected: "
|
|
||||||
<< client->getRemoteAddress()
|
|
||||||
<< std::endl;
|
|
||||||
|
|
||||||
m_player_count++;
|
|
||||||
|
|
||||||
std::thread(&GameServer::handle_client, this, client, m_player_count).detach();
|
|
||||||
|
|
||||||
if(m_player_count == 2)
|
|
||||||
{
|
|
||||||
// --------------------------------------------------------------
|
|
||||||
// Slight pause to ensure the all threads have started
|
|
||||||
// --------------------------------------------------------------
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
|
||||||
|
|
||||||
if(!send_start_game_to_clients()) {
|
|
||||||
std::cerr << "Could not start game. One or both players did not recieve START_GAME" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// No need to call close of the listener.
|
|
||||||
// The connection is closed automatically when the listener object is out of scope.
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
unsigned short m_tcp_port;
|
|
||||||
unsigned short m_player_count { 0 };
|
|
||||||
unsigned short m_turns_played { 0 };
|
|
||||||
int board[3][3];
|
|
||||||
|
|
||||||
std::vector<sf::TcpSocket*> m_clients;
|
|
||||||
std::mutex m_clients_mutex;
|
|
||||||
|
|
||||||
// Loop around, receive messages from client and send them to all
|
|
||||||
// the other connected clients.
|
|
||||||
void handle_client(sf::TcpSocket* client, unsigned short player_num)
|
|
||||||
{
|
|
||||||
sf::Socket::Status status;
|
|
||||||
|
|
||||||
if(player_num == 1) {
|
|
||||||
std::cout << "Player " << player_num << " is NOUGHTS" << std::endl;
|
|
||||||
char buffer[2] = {
|
|
||||||
GameMessageType::JOIN_GAME,
|
|
||||||
Token::NOUGHTS
|
|
||||||
};
|
|
||||||
|
|
||||||
status = client->send(buffer, message_size(GameMessageType::JOIN_GAME));
|
|
||||||
|
|
||||||
if (status != sf::Socket::Status::Done)
|
|
||||||
{
|
|
||||||
std::cerr << "Error sending JOIN_GAME to player 1" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(player_num == 2) {
|
|
||||||
std::cout << "Player " << player_num << " is CROSSES" << std::endl;
|
|
||||||
char buffer[2] = {
|
|
||||||
GameMessageType::JOIN_GAME,
|
|
||||||
Token::CROSSES
|
|
||||||
};
|
|
||||||
|
|
||||||
status = client->send(buffer, message_size(GameMessageType::JOIN_GAME));
|
|
||||||
|
|
||||||
if (status != sf::Socket::Status::Done)
|
|
||||||
{
|
|
||||||
std::cerr << "Error sending JOIN_GAME to player 2" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return; // No more players please!!!
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// RECEIVING
|
|
||||||
char payload[1024];
|
|
||||||
std::memset(payload, 0, 1024);
|
|
||||||
size_t received;
|
|
||||||
sf::Socket::Status status = client->receive(payload, 1024, received);
|
|
||||||
if (status != sf::Socket::Status::Done)
|
|
||||||
{
|
|
||||||
std::cerr << "Error receiving message from client" << std::endl;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
// Actually, there is no need to print the message if the message is not a string
|
|
||||||
debug_message(payload);
|
|
||||||
|
|
||||||
broadcast_message(payload, client);
|
|
||||||
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
|
||||||
|
|
||||||
if(++m_turns_played == 9) {
|
|
||||||
send_game_over_to_clients();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything that follows only makes sense if we have a graceful way to exiting the loop.
|
|
||||||
// Remove the client from the list when done
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(m_clients_mutex);
|
|
||||||
m_clients.erase(std::remove(m_clients.begin(), m_clients.end(), client),
|
|
||||||
m_clients.end());
|
|
||||||
}
|
|
||||||
delete client;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sends `message` from `sender` to all the other connected clients
|
|
||||||
bool broadcast_message(const char *buffer, sf::TcpSocket* sender)
|
|
||||||
{
|
|
||||||
size_t msgSize { message_size(buffer[0]) };
|
|
||||||
|
|
||||||
// You might want to validate the message before you send it.
|
|
||||||
// A few reasons for that:
|
|
||||||
// 1. Make sure the message makes sense in the game.
|
|
||||||
// 2. Make sure the sender is not cheating.
|
|
||||||
// 3. First need to synchronise the players inputs (usually done in Lockstep).
|
|
||||||
// 4. Compensate for latency and perform rollbacks (usually done in Ded Reckoning).
|
|
||||||
// 5. Delay the sending of messages to make the game fairer wrt high ping players.
|
|
||||||
// This is where you can write the authoritative part of the server.
|
|
||||||
std::lock_guard<std::mutex> lock(m_clients_mutex);
|
|
||||||
for (auto& client : m_clients)
|
|
||||||
{
|
|
||||||
if (client != sender)
|
|
||||||
{
|
|
||||||
// SENDING
|
|
||||||
sf::Socket::Status status = client->send(buffer, msgSize) ;
|
|
||||||
if (status != sf::Socket::Status::Done)
|
|
||||||
{
|
|
||||||
std::cerr << "Error sending message to client" << std::endl;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr size_t message_size(const char messageType)
|
|
||||||
{
|
|
||||||
switch(messageType) {
|
|
||||||
case JOIN_GAME: return 2;
|
|
||||||
case PLACE_TOKEN: return sizeof(int) * 2 + 2;
|
|
||||||
case START_GAME: return 1;
|
|
||||||
case GAME_OVER: return 1;
|
|
||||||
default: return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void debug_message(const char *buf)
|
|
||||||
{
|
|
||||||
const unsigned char msgType = buf[0];
|
|
||||||
|
|
||||||
switch(msgType) {
|
|
||||||
case JOIN_GAME: {
|
|
||||||
std::cout << "Player Joined The Game" << std::endl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PLACE_TOKEN: {
|
|
||||||
const unsigned char *row { (unsigned char* )buf + 1 };
|
|
||||||
const unsigned char *col { (unsigned char* )buf + 1 + sizeof(int) };
|
|
||||||
unsigned int rowI = be32toh(*((unsigned int *) row));
|
|
||||||
unsigned int colI = be32toh(*((unsigned int *) col));
|
|
||||||
std::cout << "Player Placed A Token: (" << rowI << ", " << colI << ")" << std::endl;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
GameServer server(4331);
|
|
||||||
|
|
||||||
server.tcp_start();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
SFML_PATH=/usr/local/Cellar/sfml/2.6.1/
|
|
||||||
CXXFLAGS= -std=c++14 -Wall -Wpedantic -I${SFML_PATH}include/
|
|
||||||
LDFLAGS=-L${SFML_PATH}lib/
|
|
||||||
CFLAGS=-g -lsfml-graphics -lsfml-window -lsfml-system -lsfml-network -pthread
|
|
||||||
CPPFLAGS=
|
|
||||||
LDLIBS=
|
|
||||||
LIBS=
|
|
||||||
CPP=g++
|
|
||||||
|
|
||||||
all: server
|
|
||||||
|
|
||||||
server: GameServer.o
|
|
||||||
$(CPP) $(CXXFLAGS) $(LDFLAGS) $(LIBS) $^ -o $@ $(CFLAGS)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
\rm -f *.o server
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
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),
|
|
||||||
START_GAME(3),
|
|
||||||
GAME_OVER(4);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun fromByte(id: Byte) = entries.first { it.id == id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class GameMessage(val type: GameMessageType) : Serializable {
|
|
||||||
|
|
||||||
override fun serialize() = byteArrayOf(type.id)
|
|
||||||
|
|
||||||
object StartGameMessage : GameMessage(GameMessageType.START_GAME)
|
|
||||||
object GameOverMessage : GameMessage(GameMessageType.GAME_OVER)
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
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("152.105.66.120", 4331, serverChannel, clientChannel)
|
|
||||||
|
|
||||||
addScreen(FirstScreen(this, clientChannel, serverChannel))
|
|
||||||
|
|
||||||
addScreen(GameOverScreen(0))
|
|
||||||
|
|
||||||
setScreen<FirstScreen>()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dispose() {
|
|
||||||
super.dispose()
|
|
||||||
serverChannel.close()
|
|
||||||
clientChannel.close()
|
|
||||||
networkHandler.disposeSafely()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
||||||
GameMessageType.START_GAME -> GameMessage.StartGameMessage
|
|
||||||
|
|
||||||
GameMessageType.GAME_OVER -> GameMessage.GameOverMessage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dispose() {
|
|
||||||
coroutineScope.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val TAG = NetworkHandler::class.simpleName!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
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 var gameStarted = false
|
|
||||||
|
|
||||||
private var gameOver = false
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if (gameOver) {
|
|
||||||
game.setScreen<GameOverScreen>()
|
|
||||||
disposeSafely()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
is GameMessage.StartGameMessage -> {
|
|
||||||
Gdx.app.log(TAG, "The Server has started the game")
|
|
||||||
|
|
||||||
gameStarted = true
|
|
||||||
}
|
|
||||||
|
|
||||||
is GameMessage.GameOverMessage -> {
|
|
||||||
Gdx.app.log(TAG, "The Server has sent Game Over")
|
|
||||||
|
|
||||||
gameStarted = false
|
|
||||||
gameOver = 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(!gameStarted || !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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
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(private val winType: Int) : KtxScreen {
|
|
||||||
private var label = winType.toString()
|
|
||||||
private val font = BitmapFont()
|
|
||||||
private val batch = SpriteBatch()
|
|
||||||
|
|
||||||
override fun show() {
|
|
||||||
when (winType) {
|
|
||||||
0 -> label = "You Win!"
|
|
||||||
1 -> label = "You Lose."
|
|
||||||
2 -> label = "Game Over!"
|
|
||||||
else -> "Invalid type!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun render(delta: Float) {
|
|
||||||
clearScreen(red = 0.7f, green = 0.7f, blue = 0.7f)
|
|
||||||
|
|
||||||
batch.use {
|
|
||||||
font.draw(it, label, 10f, 10f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
|
|
||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
gradlePluginPortal()
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath "io.github.fourlastor:construo:2.1.0"
|
|
||||||
if(enableGraalNative == 'true') {
|
|
||||||
classpath "org.graalvm.buildtools.native:org.graalvm.buildtools.native.gradle.plugin:0.9.28"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
plugins {
|
|
||||||
id "application"
|
|
||||||
}
|
|
||||||
apply plugin: 'io.github.fourlastor.construo'
|
|
||||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
|
||||||
|
|
||||||
|
|
||||||
import io.github.fourlastor.construo.Target
|
|
||||||
|
|
||||||
sourceSets.main.resources.srcDirs += [ rootProject.file('assets').path ]
|
|
||||||
application.mainClass = 'u0012604.tictactoe.lwjgl3.Lwjgl3Launcher'
|
|
||||||
eclipse.project.name = appName + '-lwjgl3'
|
|
||||||
java.sourceCompatibility = 8
|
|
||||||
java.targetCompatibility = 8
|
|
||||||
if (JavaVersion.current().isJava9Compatible()) {
|
|
||||||
compileJava.options.release.set(8)
|
|
||||||
}
|
|
||||||
kotlin.compilerOptions.jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8)
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation "com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion"
|
|
||||||
implementation "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-desktop"
|
|
||||||
implementation "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop"
|
|
||||||
implementation "com.badlogicgames.gdx:gdx-lwjgl3-angle:$gdxVersion"
|
|
||||||
implementation "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop"
|
|
||||||
implementation project(':core')
|
|
||||||
|
|
||||||
if(enableGraalNative == 'true') {
|
|
||||||
implementation "io.github.berstanio:gdx-svmhelper-backend-lwjgl3:$graalHelperVersion"
|
|
||||||
implementation "io.github.berstanio:gdx-svmhelper-extension-freetype:$graalHelperVersion"
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def os = System.properties['os.name'].toLowerCase(Locale.ROOT)
|
|
||||||
|
|
||||||
run {
|
|
||||||
workingDir = rootProject.file('assets').path
|
|
||||||
// You can uncomment the next line if your IDE claims a build failure even when the app closed properly.
|
|
||||||
//setIgnoreExitValue(true)
|
|
||||||
|
|
||||||
if (os.contains('mac')) jvmArgs += "-XstartOnFirstThread"
|
|
||||||
}
|
|
||||||
|
|
||||||
jar {
|
|
||||||
// sets the name of the .jar file this produces to the name of the game or app, with the version after.
|
|
||||||
archiveFileName.set("${appName}-${projectVersion}.jar")
|
|
||||||
// the duplicatesStrategy matters starting in Gradle 7.0; this setting works.
|
|
||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
|
||||||
dependsOn configurations.runtimeClasspath
|
|
||||||
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
|
|
||||||
// these "exclude" lines remove some unnecessary duplicate files in the output JAR.
|
|
||||||
exclude('META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA')
|
|
||||||
dependencies {
|
|
||||||
exclude('META-INF/INDEX.LIST', 'META-INF/maven/**')
|
|
||||||
}
|
|
||||||
// setting the manifest makes the JAR runnable.
|
|
||||||
// enabling native access helps avoid a warning when Java 24 or later runs the JAR.
|
|
||||||
manifest {
|
|
||||||
attributes 'Main-Class': application.mainClass, 'Enable-Native-Access': 'ALL-UNNAMED'
|
|
||||||
}
|
|
||||||
// this last step may help on some OSes that need extra instruction to make runnable JARs.
|
|
||||||
doLast {
|
|
||||||
file(archiveFile).setExecutable(true, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Builds a JAR that only includes the files needed to run on macOS, not Windows or Linux.
|
|
||||||
// The file size for a Mac-only JAR is about 7MB smaller than a cross-platform JAR.
|
|
||||||
tasks.register("jarMac") {
|
|
||||||
dependsOn("jar")
|
|
||||||
group("build")
|
|
||||||
jar.archiveFileName.set("${appName}-${projectVersion}-mac.jar")
|
|
||||||
jar.exclude("windows/x86/**", "windows/x64/**", "linux/arm32/**", "linux/arm64/**", "linux/x64/**", "**/*.dll", "**/*.so",
|
|
||||||
'META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA')
|
|
||||||
dependencies {
|
|
||||||
jar.exclude("windows/x86/**", "windows/x64/**", "linux/arm32/**", "linux/arm64/**", "linux/x64/**",
|
|
||||||
'META-INF/INDEX.LIST', 'META-INF/maven/**')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Builds a JAR that only includes the files needed to run on Linux, not Windows or macOS.
|
|
||||||
// The file size for a Linux-only JAR is about 5MB smaller than a cross-platform JAR.
|
|
||||||
tasks.register("jarLinux") {
|
|
||||||
dependsOn("jar")
|
|
||||||
group("build")
|
|
||||||
jar.archiveFileName.set("${appName}-${projectVersion}-linux.jar")
|
|
||||||
jar.exclude("windows/x86/**", "windows/x64/**", "macos/arm64/**", "macos/x64/**", "**/*.dll", "**/*.dylib",
|
|
||||||
'META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA')
|
|
||||||
dependencies {
|
|
||||||
jar.exclude("windows/x86/**", "windows/x64/**", "macos/arm64/**", "macos/x64/**",
|
|
||||||
'META-INF/INDEX.LIST', 'META-INF/maven/**')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Builds a JAR that only includes the files needed to run on Windows, not Linux or macOS.
|
|
||||||
// The file size for a Windows-only JAR is about 6MB smaller than a cross-platform JAR.
|
|
||||||
tasks.register("jarWin") {
|
|
||||||
dependsOn("jar")
|
|
||||||
group("build")
|
|
||||||
jar.archiveFileName.set("${appName}-${projectVersion}-win.jar")
|
|
||||||
jar.exclude("macos/arm64/**", "macos/x64/**", "linux/arm32/**", "linux/arm64/**", "linux/x64/**", "**/*.dylib", "**/*.so",
|
|
||||||
'META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA')
|
|
||||||
dependencies {
|
|
||||||
jar.exclude("macos/arm64/**", "macos/x64/**", "linux/arm32/**", "linux/arm64/**", "linux/x64/**",
|
|
||||||
'META-INF/INDEX.LIST', 'META-INF/maven/**')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
construo {
|
|
||||||
// name of the executable
|
|
||||||
name.set(appName)
|
|
||||||
// human-readable name, used for example in the `.app` name for macOS
|
|
||||||
humanName.set(appName)
|
|
||||||
|
|
||||||
targets.configure {
|
|
||||||
register("linuxX64", Target.Linux) {
|
|
||||||
architecture.set(Target.Architecture.X86_64)
|
|
||||||
jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_linux_hotspot_17.0.15_6.tar.gz")
|
|
||||||
// Linux does not currently have a way to set the icon on the executable
|
|
||||||
}
|
|
||||||
register("macM1", Target.MacOs) {
|
|
||||||
architecture.set(Target.Architecture.AARCH64)
|
|
||||||
jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.15_6.tar.gz")
|
|
||||||
// macOS needs an identifier
|
|
||||||
identifier.set("u0012604.tictactoe." + appName)
|
|
||||||
// Optional: icon for macOS, as an ICNS file
|
|
||||||
macIcon.set(project.file("icons/logo.icns"))
|
|
||||||
}
|
|
||||||
register("macX64", Target.MacOs) {
|
|
||||||
architecture.set(Target.Architecture.X86_64)
|
|
||||||
jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_mac_hotspot_17.0.15_6.tar.gz")
|
|
||||||
// macOS needs an identifier
|
|
||||||
identifier.set("u0012604.tictactoe." + appName)
|
|
||||||
// Optional: icon for macOS, as an ICNS file
|
|
||||||
macIcon.set(project.file("icons/logo.icns"))
|
|
||||||
}
|
|
||||||
register("winX64", Target.Windows) {
|
|
||||||
architecture.set(Target.Architecture.X86_64)
|
|
||||||
// Optional: icon for Windows, as a PNG
|
|
||||||
icon.set(project.file("icons/logo.png"))
|
|
||||||
jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_windows_hotspot_17.0.15_6.zip")
|
|
||||||
// Uncomment the next line to show a console when the game runs, to print messages.
|
|
||||||
//useConsole.set(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equivalent to the jar task; here for compatibility with gdx-setup.
|
|
||||||
tasks.register('dist') {
|
|
||||||
dependsOn 'jar'
|
|
||||||
}
|
|
||||||
|
|
||||||
distributions {
|
|
||||||
main {
|
|
||||||
contents {
|
|
||||||
into('libs') {
|
|
||||||
project.configurations.runtimeClasspath.files.findAll { file ->
|
|
||||||
file.getName() != project.tasks.jar.outputs.files.singleFile.name
|
|
||||||
}.each { file ->
|
|
||||||
exclude file.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startScripts.dependsOn(':lwjgl3:jar')
|
|
||||||
startScripts.classpath = project.tasks.jar.outputs.files
|
|
||||||
|
|
||||||
if(enableGraalNative == 'true') {
|
|
||||||
apply from: file("nativeimage.gradle")
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 9.3 KiB |
@@ -1,54 +0,0 @@
|
|||||||
|
|
||||||
project(":lwjgl3") {
|
|
||||||
apply plugin: "org.graalvm.buildtools.native"
|
|
||||||
|
|
||||||
graalvmNative {
|
|
||||||
binaries {
|
|
||||||
main {
|
|
||||||
imageName = appName
|
|
||||||
mainClass = application.mainClass
|
|
||||||
requiredVersion = '23.0'
|
|
||||||
buildArgs.add("-march=compatibility")
|
|
||||||
jvmArgs.addAll("-Dfile.encoding=UTF8")
|
|
||||||
sharedLibrary = false
|
|
||||||
resources.autodetect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run {
|
|
||||||
doNotTrackState("Running the app should not be affected by Graal.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modified from https://lyze.dev/2021/04/29/libGDX-Internal-Assets-List/ ; thanks again, Lyze!
|
|
||||||
// This creates a resource-config.json file based on the contents of the assets folder (and the libGDX icons).
|
|
||||||
// This file is used by Graal Native to embed those specific files.
|
|
||||||
// This has to run before nativeCompile, so it runs at the start of an unrelated resource-handling command.
|
|
||||||
generateResourcesConfigFile.doFirst {
|
|
||||||
def assetsFolder = new File("${project.rootDir}/assets/")
|
|
||||||
def lwjgl3 = project(':lwjgl3')
|
|
||||||
def resFolder = new File("${lwjgl3.projectDir}/src/main/resources/META-INF/native-image/${lwjgl3.ext.appName}")
|
|
||||||
resFolder.mkdirs()
|
|
||||||
def resFile = new File(resFolder, "resource-config.json")
|
|
||||||
resFile.delete()
|
|
||||||
resFile.append(
|
|
||||||
"""{
|
|
||||||
"resources":{
|
|
||||||
"includes":[
|
|
||||||
{
|
|
||||||
"pattern": ".*(""")
|
|
||||||
// This adds every filename in the assets/ folder to a pattern that adds those files as resources.
|
|
||||||
fileTree(assetsFolder).each {
|
|
||||||
// The backslash-Q and backslash-E escape the start and end of a literal string, respectively.
|
|
||||||
resFile.append("\\\\Q${it.name}\\\\E|")
|
|
||||||
}
|
|
||||||
// We also match all of the window icon images this way and the font files that are part of libGDX.
|
|
||||||
resFile.append(
|
|
||||||
"""libgdx.+\\\\.png|lsans.+)"
|
|
||||||
}
|
|
||||||
]},
|
|
||||||
"bundles":[]
|
|
||||||
}"""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
@file:JvmName("Lwjgl3Launcher")
|
|
||||||
|
|
||||||
package u0012604.tictactoe.lwjgl3
|
|
||||||
|
|
||||||
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application
|
|
||||||
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration
|
|
||||||
import u0012604.tictactoe.Main
|
|
||||||
|
|
||||||
/** Launches the desktop (LWJGL3) application. */
|
|
||||||
fun main() {
|
|
||||||
// This handles macOS support and helps on Windows.
|
|
||||||
if (StartupHelper.startNewJvmIfRequired())
|
|
||||||
return
|
|
||||||
Lwjgl3Application(Main(), Lwjgl3ApplicationConfiguration().apply {
|
|
||||||
setTitle("NetworkedTicTacToe")
|
|
||||||
//// Vsync limits the frames per second to what your hardware can display, and helps eliminate
|
|
||||||
//// screen tearing. This setting doesn't always work on Linux, so the line after is a safeguard.
|
|
||||||
useVsync(true)
|
|
||||||
//// Limits FPS to the refresh rate of the currently active monitor, plus 1 to try to match fractional
|
|
||||||
//// refresh rates. The Vsync setting above should limit the actual FPS to match the monitor.
|
|
||||||
setForegroundFPS(Lwjgl3ApplicationConfiguration.getDisplayMode().refreshRate + 1)
|
|
||||||
//// If you remove the above line and set Vsync to false, you can get unlimited FPS, which can be
|
|
||||||
//// useful for testing performance, but can also be very stressful to some hardware.
|
|
||||||
//// You may also need to configure GPU drivers to fully disable Vsync; this can cause screen tearing.
|
|
||||||
|
|
||||||
|
|
||||||
setWindowedMode(640, 480)
|
|
||||||
//// You can change these files; they are in lwjgl3/src/main/resources/ .
|
|
||||||
//// They can also be loaded from the root of assets/ .
|
|
||||||
setWindowIcon(*(arrayOf(128, 64, 32, 16).map { "libgdx$it.png" }.toTypedArray()))
|
|
||||||
|
|
||||||
//// This should improve compatibility with Windows machines with buggy OpenGL drivers, Macs
|
|
||||||
//// with Apple Silicon that have to emulate compatibility with OpenGL anyway, and more.
|
|
||||||
//// This uses the dependency `com.badlogicgames.gdx:gdx-lwjgl3-angle` to function.
|
|
||||||
//// You can choose to remove the following line and the mentioned dependency if you want; they
|
|
||||||
//// are not intended for games that use GL30 (which is compatibility with OpenGL ES 3.0).
|
|
||||||
setOpenGLEmulation(Lwjgl3ApplicationConfiguration.GLEmulation.ANGLE_GLES20, 0, 0)
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
|
|
||||||
/*
|
|
||||||
* Copyright 2020 damios
|
|
||||||
*
|
|
||||||
* 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:
|
|
||||||
* https://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.
|
|
||||||
*/
|
|
||||||
//Note, the above license and copyright applies to this file only.
|
|
||||||
package u0012604.tictactoe.lwjgl3
|
|
||||||
|
|
||||||
import com.badlogic.gdx.Version
|
|
||||||
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3NativesLoader
|
|
||||||
import org.lwjgl.system.JNI
|
|
||||||
import org.lwjgl.system.macosx.LibC
|
|
||||||
import org.lwjgl.system.macosx.ObjCRuntime
|
|
||||||
import java.io.BufferedReader
|
|
||||||
import java.io.File
|
|
||||||
import java.io.InputStreamReader
|
|
||||||
import java.lang.management.ManagementFactory
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds some utilities to ensure that the JVM was started with the
|
|
||||||
* `-XstartOnFirstThread` argument, which is required on macOS for LWJGL 3
|
|
||||||
* to function. Also helps on Windows when users have names with characters from
|
|
||||||
* outside the Latin alphabet, a common cause of startup crashes.
|
|
||||||
*
|
|
||||||
* [Based on this java-gaming.org post by kappa](https://jvm-gaming.org/t/starting-jvm-on-mac-with-xstartonfirstthread-programmatically/57547)
|
|
||||||
* @author damios
|
|
||||||
*/
|
|
||||||
class StartupHelper private constructor() {
|
|
||||||
init {
|
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val JVM_RESTARTED_ARG = "jvmIsRestarted"
|
|
||||||
/**
|
|
||||||
* Starts a new JVM if the application was started on macOS without the
|
|
||||||
* `-XstartOnFirstThread` argument. This also includes some code for
|
|
||||||
* Windows, for the case where the user's home directory includes certain
|
|
||||||
* non-Latin-alphabet characters (without this code, most LWJGL3 apps fail
|
|
||||||
* immediately for those users). Returns whether a new JVM was started and
|
|
||||||
* thus no code should be executed.
|
|
||||||
*
|
|
||||||
* **Usage:**
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* fun main() {
|
|
||||||
* if (StartupHelper.startNewJvmIfRequired(true)) return // This handles macOS support and helps on Windows.
|
|
||||||
* // after this is the actual main method code
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param redirectOutput
|
|
||||||
* whether the output of the new JVM should be rerouted to the
|
|
||||||
* old JVM, so it can be accessed in the same place; keeps the
|
|
||||||
* old JVM running if enabled
|
|
||||||
* @return whether a new JVM was started and thus no code should be executed
|
|
||||||
* in this one
|
|
||||||
*/
|
|
||||||
@JvmOverloads
|
|
||||||
fun startNewJvmIfRequired(redirectOutput: Boolean = true): Boolean {
|
|
||||||
val osName = System.getProperty("os.name").lowercase(Locale.ROOT)
|
|
||||||
if (!osName.contains("mac")) {
|
|
||||||
if (osName.contains("windows")) {
|
|
||||||
// Here, we are trying to work around an issue with how LWJGL3 loads its extracted .dll files.
|
|
||||||
// By default, LWJGL3 extracts to the directory specified by "java.io.tmpdir", which is usually the user's home.
|
|
||||||
// If the user's name has non-ASCII (or some non-alphanumeric) characters in it, that would fail.
|
|
||||||
// By extracting to the relevant "ProgramData" folder, which is usually "C:\ProgramData", we avoid this.
|
|
||||||
// We also temporarily change the "user.name" property to one without any chars that would be invalid.
|
|
||||||
// We revert our changes immediately after loading LWJGL3 natives.
|
|
||||||
val programData = System.getenv("ProgramData") ?: "C:\\Temp\\"
|
|
||||||
val prevTmpDir = System.getProperty("java.io.tmpdir", programData)
|
|
||||||
val prevUser = System.getProperty("user.name", "libGDX_User")
|
|
||||||
System.setProperty("java.io.tmpdir", "$programData/libGDX-temp")
|
|
||||||
System.setProperty("user.name", "User_${prevUser.hashCode()}_GDX${Version.VERSION}".replace('.', '_'))
|
|
||||||
Lwjgl3NativesLoader.load()
|
|
||||||
System.setProperty("java.io.tmpdir", prevTmpDir)
|
|
||||||
System.setProperty("user.name", prevUser)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// There is no need for -XstartOnFirstThread on Graal native image
|
|
||||||
if (System.getProperty("org.graalvm.nativeimage.imagecode", "").isNotEmpty()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if we are already on the main thread, such as from running via Construo.
|
|
||||||
val objcMsgSend = ObjCRuntime.getLibrary().getFunctionAddress("objc_msgSend")
|
|
||||||
val nsThread = ObjCRuntime.objc_getClass("NSThread")
|
|
||||||
val currentThread = JNI.invokePPP(nsThread, ObjCRuntime.sel_getUid("currentThread"), objcMsgSend)
|
|
||||||
val isMainThread = JNI.invokePPZ(currentThread, ObjCRuntime.sel_getUid("isMainThread"), objcMsgSend)
|
|
||||||
if (isMainThread) return false
|
|
||||||
|
|
||||||
val pid = LibC.getpid()
|
|
||||||
|
|
||||||
// check whether -XstartOnFirstThread is enabled
|
|
||||||
if ("1" == System.getenv("JAVA_STARTED_ON_FIRST_THREAD_$pid")) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// check whether the JVM was previously restarted
|
|
||||||
// avoids looping, but most certainly leads to a crash
|
|
||||||
if ("true" == System.getProperty(JVM_RESTARTED_ARG)) {
|
|
||||||
System.err.println(
|
|
||||||
"There was a problem evaluating whether the JVM was started with the -XstartOnFirstThread argument."
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restart the JVM with -XstartOnFirstThread
|
|
||||||
val jvmArgs = ArrayList<String?>()
|
|
||||||
val separator = System.getProperty("file.separator", "/")
|
|
||||||
// The following line is used assuming you target Java 8, the minimum for LWJGL3.
|
|
||||||
val javaExecPath = System.getProperty("java.home") + separator + "bin" + separator + "java"
|
|
||||||
// If targeting Java 9 or higher, you could use the following instead of the above line:
|
|
||||||
//String javaExecPath = ProcessHandle.current().info().command().orElseThrow();
|
|
||||||
if (!File(javaExecPath).exists()) {
|
|
||||||
System.err.println(
|
|
||||||
"A Java installation could not be found. If you are distributing this app with a bundled JRE, be sure to set the -XstartOnFirstThread argument manually!"
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
jvmArgs.add(javaExecPath)
|
|
||||||
jvmArgs.add("-XstartOnFirstThread")
|
|
||||||
jvmArgs.add("-D$JVM_RESTARTED_ARG=true")
|
|
||||||
jvmArgs.addAll(ManagementFactory.getRuntimeMXBean().inputArguments)
|
|
||||||
jvmArgs.add("-cp")
|
|
||||||
jvmArgs.add(System.getProperty("java.class.path"))
|
|
||||||
var mainClass = System.getenv("JAVA_MAIN_CLASS_$pid")
|
|
||||||
if (mainClass == null) {
|
|
||||||
val trace = Thread.currentThread().stackTrace
|
|
||||||
mainClass = if (trace.isNotEmpty()) {
|
|
||||||
trace[trace.size - 1].className
|
|
||||||
} else {
|
|
||||||
System.err.println("The main class could not be determined.")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jvmArgs.add(mainClass)
|
|
||||||
try {
|
|
||||||
if (!redirectOutput) {
|
|
||||||
val processBuilder = ProcessBuilder(jvmArgs)
|
|
||||||
processBuilder.start()
|
|
||||||
} else {
|
|
||||||
val process = ProcessBuilder(jvmArgs)
|
|
||||||
.redirectErrorStream(true).start()
|
|
||||||
val processOutput = BufferedReader(
|
|
||||||
InputStreamReader(process.inputStream)
|
|
||||||
)
|
|
||||||
var line: String?
|
|
||||||
while (processOutput.readLine().also { line = it } != null) {
|
|
||||||
println(line)
|
|
||||||
}
|
|
||||||
process.waitFor()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
System.err.println("There was a problem restarting the JVM")
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 806 B |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
BIN
Raw Assets/player-frame-pressed.aseprite
Normal file
BIN
Raw Assets/player-frame.aseprite
Normal file
BIN
Raw Assets/prepared-item-frame.aseprite
Normal file
@@ -2,6 +2,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
<uses-feature android:glEsVersion="0x00020000" android:required="true"/>
|
<uses-feature android:glEsVersion="0x00020000" android:required="true"/>
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:fullBackupContent="true"
|
android:fullBackupContent="true"
|
||||||
@@ -14,7 +15,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="com.iofferyoutea.WitchQueen.android.AndroidLauncher"
|
android:name="com.iofferyoutea.WitchQueen.android.AndroidLauncher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:screenOrientation="landscape"
|
android:screenOrientation="portrait"
|
||||||
android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|screenLayout"
|
android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|screenLayout"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class AndroidLauncher : AndroidApplication() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
initialize(Main(), AndroidApplicationConfiguration().apply {
|
initialize(Main(), AndroidApplicationConfiguration().apply {
|
||||||
// Configure your application here.
|
// Configure your application here.
|
||||||
useImmersiveMode = true // Recommended, but not required.
|
//useImmersiveMode = true // Recommended, but not required.
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
WitchQueen/assets/default_flipped.png
Normal file
|
After Width: | Height: | Size: 94 B |
BIN
WitchQueen/assets/grey-plus.png
Normal file
|
After Width: | Height: | Size: 108 B |
BIN
WitchQueen/assets/player-frame-down.png
Normal file
|
After Width: | Height: | Size: 197 B |
BIN
WitchQueen/assets/player-frame-up.png
Normal file
|
After Width: | Height: | Size: 216 B |
BIN
WitchQueen/assets/player-frame.png
Normal file
|
After Width: | Height: | Size: 211 B |
BIN
WitchQueen/assets/prepared-item-frame.png
Normal file
|
After Width: | Height: | Size: 154 B |
BIN
WitchQueen/assets/red-x.png
Normal file
|
After Width: | Height: | Size: 129 B |
BIN
WitchQueen/assets/transparent.png
Normal file
|
After Width: | Height: | Size: 81 B |
@@ -7,7 +7,6 @@ dependencies {
|
|||||||
api "com.badlogicgames.gdx:gdx-box2d:$gdxVersion"
|
api "com.badlogicgames.gdx:gdx-box2d:$gdxVersion"
|
||||||
api "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
|
api "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
|
||||||
api "com.badlogicgames.gdx:gdx:$gdxVersion"
|
api "com.badlogicgames.gdx:gdx:$gdxVersion"
|
||||||
api "com.github.kotcrab.vis-ui:vis-ui:$visUiVersion"
|
|
||||||
api "io.github.libktx:ktx-actors:$ktxVersion"
|
api "io.github.libktx:ktx-actors:$ktxVersion"
|
||||||
api "io.github.libktx:ktx-ai:$ktxVersion"
|
api "io.github.libktx:ktx-ai:$ktxVersion"
|
||||||
api "io.github.libktx:ktx-app:$ktxVersion"
|
api "io.github.libktx:ktx-app:$ktxVersion"
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.iofferyoutea.WitchQueen
|
||||||
|
|
||||||
|
class Client(val hostAddress: String, val hostPort: Int, val playerProfile: PlayerProfile) {
|
||||||
|
val preparedItems: MutableList<Int> = mutableListOf(0, 0, 0, 0) // Item ID. 0 for no item
|
||||||
|
lateinit var map: Map
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package com.iofferyoutea.WitchQueen
|
||||||
|
|
||||||
|
class Game(val clients: Array<Client>) {
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.iofferyoutea.WitchQueen
|
package com.iofferyoutea.WitchQueen
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Game
|
||||||
import com.badlogic.gdx.graphics.Texture
|
import com.badlogic.gdx.graphics.Texture
|
||||||
import com.badlogic.gdx.graphics.Texture.TextureFilter.Linear
|
import com.badlogic.gdx.graphics.Texture.TextureFilter.Linear
|
||||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||||
@@ -10,13 +11,14 @@ import ktx.assets.disposeSafely
|
|||||||
import ktx.assets.toInternalFile
|
import ktx.assets.toInternalFile
|
||||||
import ktx.async.KtxAsync
|
import ktx.async.KtxAsync
|
||||||
import ktx.graphics.use
|
import ktx.graphics.use
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
class Main : KtxGame<KtxScreen>() {
|
class Main : KtxGame<KtxScreen>() {
|
||||||
override fun create() {
|
override fun create() {
|
||||||
KtxAsync.initiate()
|
KtxAsync.initiate()
|
||||||
|
|
||||||
addScreen(FirstScreen())
|
addScreen(MainMenu())
|
||||||
setScreen<FirstScreen>()
|
setScreen<MainMenu>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package com.iofferyoutea.WitchQueen
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Gdx
|
||||||
|
import com.badlogic.gdx.graphics.Texture
|
||||||
|
import com.badlogic.gdx.graphics.g2d.BitmapFont
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.Stage
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Container
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable
|
||||||
|
import ktx.app.KtxScreen
|
||||||
|
import ktx.app.clearScreen
|
||||||
|
import org.w3c.dom.Text
|
||||||
|
|
||||||
|
class MainMenu : KtxScreen {
|
||||||
|
val table = Table().apply {
|
||||||
|
debug = true
|
||||||
|
setFillParent(true)
|
||||||
|
}
|
||||||
|
val stage = Stage().apply {
|
||||||
|
Gdx.input.inputProcessor = this
|
||||||
|
addActor(table)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default
|
||||||
|
val defaultButtonTrd = TextureRegionDrawable(Texture("default.png"))
|
||||||
|
val flippedDefaultButtonTrd = TextureRegionDrawable(Texture("default_flipped.png"))
|
||||||
|
val defaultTextButtonStyle = TextButton.TextButtonStyle (
|
||||||
|
defaultButtonTrd,
|
||||||
|
flippedDefaultButtonTrd,
|
||||||
|
defaultButtonTrd,
|
||||||
|
BitmapFont()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map
|
||||||
|
|
||||||
|
// Lobby
|
||||||
|
// Start off only showing Host and Join button. When selected show menu for that option.
|
||||||
|
// Host is normal lobby
|
||||||
|
// Join will bring up menu with games on local network
|
||||||
|
val hostButton = TextButton("Host", defaultTextButtonStyle)
|
||||||
|
val joinButton = TextButton("Join", defaultTextButtonStyle)
|
||||||
|
val hostOrJoinVerticalGroup = VerticalGroup().apply {
|
||||||
|
addActor(hostButton)
|
||||||
|
addActor(joinButton)
|
||||||
|
}
|
||||||
|
val lobbyContainer = Container<Actor>(hostOrJoinVerticalGroup)
|
||||||
|
|
||||||
|
//region Play
|
||||||
|
val casualButton = TextButton("Casual", defaultTextButtonStyle)
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
val everythingVerticalGroup = VerticalGroup().apply {
|
||||||
|
addActor(lobbyContainer)
|
||||||
|
addActor(casualButton)
|
||||||
|
table.add(this)
|
||||||
|
Gdx.app.log("MainMenu", stage.width.toString())
|
||||||
|
//Gdx.app.log("MainMenu", table.width.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun update(delta: Float) {
|
||||||
|
if (hostButton.isPressed) {
|
||||||
|
Gdx.app.log("MainMenu", "Host button pressed")
|
||||||
|
}
|
||||||
|
if (joinButton.isPressed) {
|
||||||
|
Gdx.app.log("MainMenu", "Join button pressed")
|
||||||
|
}
|
||||||
|
if (casualButton.isPressed) {
|
||||||
|
Gdx.app.log("MainMenu", "Casual button pressed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun render(delta: Float) {
|
||||||
|
clearScreen(0f,0f, 0f)
|
||||||
|
|
||||||
|
update(delta)
|
||||||
|
|
||||||
|
stage.act(delta)
|
||||||
|
stage.draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resize(width: Int, height: Int) {
|
||||||
|
Gdx.app.log("MainMenu", "resize called! New width: $width, new height $height")
|
||||||
|
everythingVerticalGroup.setSize(width.toFloat(), height.toFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
stage.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.iofferyoutea.WitchQueen
|
||||||
|
|
||||||
|
class Map(val mapType: MapType) {
|
||||||
|
val rooms = Array(2) { Array<Room>(2) { Room(mapType) } } // We use an array instead of list i think
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.iofferyoutea.WitchQueen
|
||||||
|
|
||||||
|
enum class MapType {
|
||||||
|
DUNGEON, STREETS
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.iofferyoutea.WitchQueen
|
||||||
|
|
||||||
|
enum class PlayerState {
|
||||||
|
IDLE, LOOTING, FIGHTING
|
||||||
|
}
|
||||||
|
|
||||||
|
class Player(val map: Map) {
|
||||||
|
var currentState = PlayerState.IDLE
|
||||||
|
val currentRoom = arrayOf(0, 0) // Make this a spawn room or something
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.iofferyoutea.WitchQueen
|
||||||
|
|
||||||
|
class PlayerProfile {
|
||||||
|
var iconPath = "default.png"
|
||||||
|
var username = "Default"
|
||||||
|
val availableItems: MutableList<Int> = mutableListOf() // Use Item IDs?
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.iofferyoutea.WitchQueen
|
||||||
|
|
||||||
|
enum class RoomActivity {
|
||||||
|
EMPTY, LOOT, FIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
class Room(val mapType: MapType) {
|
||||||
|
var availableActivities: MutableList<RoomActivity> = mutableListOf()
|
||||||
|
init {
|
||||||
|
// Generate Room in this init block!
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -162,42 +162,3 @@ Thumbs.db
|
|||||||
## You could also add that configuration to the text in nativeimage.gradle .
|
## You could also add that configuration to the text in nativeimage.gradle .
|
||||||
## You should delete or comment out the next line if you have configuration in a different resource-config.json .
|
## You should delete or comment out the next line if you have configuration in a different resource-config.json .
|
||||||
**/resource-config.json
|
**/resource-config.json
|
||||||
|
|
||||||
# Created by https://www.toptal.com/developers/gitignore/api/c++
|
|
||||||
# Edit at https://www.toptal.com/developers/gitignore?templates=c++
|
|
||||||
|
|
||||||
### C++ ###
|
|
||||||
# Prerequisites
|
|
||||||
*.d
|
|
||||||
|
|
||||||
# Compiled Object files
|
|
||||||
*.slo
|
|
||||||
*.lo
|
|
||||||
*.o
|
|
||||||
*.obj
|
|
||||||
|
|
||||||
# Precompiled Headers
|
|
||||||
*.gch
|
|
||||||
*.pch
|
|
||||||
|
|
||||||
# Compiled Dynamic libraries
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
*.dll
|
|
||||||
|
|
||||||
# Fortran module files
|
|
||||||
*.mod
|
|
||||||
*.smod
|
|
||||||
|
|
||||||
# Compiled Static libraries
|
|
||||||
*.lai
|
|
||||||
*.la
|
|
||||||
*.a
|
|
||||||
*.lib
|
|
||||||
|
|
||||||
# Executables
|
|
||||||
*.exe
|
|
||||||
*.out
|
|
||||||
*.app
|
|
||||||
GameServer/server
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/c++
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# NetworkedTicTacToe
|
# uitest
|
||||||
|
|
||||||
A [libGDX](https://libgdx.com/) project generated with [gdx-liftoff](https://github.com/libgdx/gdx-liftoff).
|
A [libGDX](https://libgdx.com/) project generated with [gdx-liftoff](https://github.com/libgdx/gdx-liftoff).
|
||||||
|
|
||||||
@@ -7,7 +7,6 @@ This project was generated with a Kotlin project template that includes Kotlin a
|
|||||||
## Platforms
|
## Platforms
|
||||||
|
|
||||||
- `core`: Main module with the application logic shared by all platforms.
|
- `core`: Main module with the application logic shared by all platforms.
|
||||||
- `lwjgl3`: Primary desktop platform using LWJGL3; was called 'desktop' in older docs.
|
|
||||||
- `android`: Android mobile platform. Needs Android SDK.
|
- `android`: Android mobile platform. Needs Android SDK.
|
||||||
|
|
||||||
## Gradle
|
## Gradle
|
||||||
@@ -27,8 +26,6 @@ Useful Gradle tasks and flags:
|
|||||||
- `clean`: removes `build` folders, which store compiled classes and built archives.
|
- `clean`: removes `build` folders, which store compiled classes and built archives.
|
||||||
- `eclipse`: generates Eclipse project data.
|
- `eclipse`: generates Eclipse project data.
|
||||||
- `idea`: generates IntelliJ project data.
|
- `idea`: generates IntelliJ project data.
|
||||||
- `lwjgl3:jar`: builds application's runnable jar, which can be found at `lwjgl3/build/libs`.
|
|
||||||
- `lwjgl3:run`: starts the application.
|
|
||||||
- `test`: runs unit tests (if any).
|
- `test`: runs unit tests (if any).
|
||||||
|
|
||||||
Note that most tasks that are not specific to a single project can be run with `name:` prefix, where the `name` should be replaced with the ID of a specific project.
|
Note that most tasks that are not specific to a single project can be run with `name:` prefix, where the `name` should be replaced with the ID of a specific project.
|
||||||
@@ -2,8 +2,6 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
<uses-feature android:glEsVersion="0x00020000" android:required="true"/>
|
<uses-feature android:glEsVersion="0x00020000" android:required="true"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:fullBackupContent="true"
|
android:fullBackupContent="true"
|
||||||
@@ -14,7 +12,7 @@
|
|||||||
tools:ignore="UnusedAttribute"
|
tools:ignore="UnusedAttribute"
|
||||||
android:theme="@style/GdxTheme">
|
android:theme="@style/GdxTheme">
|
||||||
<activity
|
<activity
|
||||||
android:name="u0012604.tictactoe.android.AndroidLauncher"
|
android:name="com.iofferyoutea.uitest.android.AndroidLauncher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:screenOrientation="landscape"
|
android:screenOrientation="landscape"
|
||||||
android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|screenLayout"
|
android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|screenLayout"
|
||||||
@@ -9,7 +9,7 @@ apply plugin: 'com.android.application'
|
|||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "u0012604.tictactoe"
|
namespace = "com.iofferyoutea.uitest"
|
||||||
compileSdk = 35
|
compileSdk = 35
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
@@ -31,7 +31,7 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'u0012604.tictactoe'
|
applicationId 'com.iofferyoutea.uitest'
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 35
|
targetSdkVersion 35
|
||||||
versionCode 1
|
versionCode 1
|
||||||
@@ -130,7 +130,7 @@ tasks.register('run', Exec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def adb = path + "/platform-tools/adb"
|
def adb = path + "/platform-tools/adb"
|
||||||
commandLine "$adb", 'shell', 'am', 'start', '-n', 'u0012604.tictactoe/u0012604.tictactoe.android.AndroidLauncher'
|
commandLine "$adb", 'shell', 'am', 'start', '-n', 'com.iofferyoutea.uitest/com.iofferyoutea.uitest.android.AndroidLauncher'
|
||||||
}
|
}
|
||||||
|
|
||||||
eclipse.project.name = appName + "-android"
|
eclipse.project.name = appName + "-android"
|
||||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">NetworkedTicTacToe</string>
|
<string name="app_name">uitest</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package u0012604.tictactoe.android
|
package com.iofferyoutea.uitest.android
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
|
||||||
import com.badlogic.gdx.backends.android.AndroidApplication
|
import com.badlogic.gdx.backends.android.AndroidApplication
|
||||||
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration
|
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration
|
||||||
import u0012604.tictactoe.Main
|
import com.iofferyoutea.uitest.Main
|
||||||
|
|
||||||
/** Launches the Android application. */
|
/** Launches the Android application. */
|
||||||
class AndroidLauncher : AndroidApplication() {
|
class AndroidLauncher : AndroidApplication() {
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -7,7 +7,7 @@ buildscript {
|
|||||||
maven { url = 'https://central.sonatype.com/repository/maven-snapshots/' }
|
maven { url = 'https://central.sonatype.com/repository/maven-snapshots/' }
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:8.13.2'
|
classpath "com.android.tools.build:gradle:8.9.3"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ configure(subprojects - project(':android')) {
|
|||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
version = "$projectVersion"
|
version = "$projectVersion"
|
||||||
ext.appName = 'NetworkedTicTacToe'
|
ext.appName = 'uitest'
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
// You may want to remove the following line if you have errors downloading dependencies.
|
// You may want to remove the following line if you have errors downloading dependencies.
|
||||||
@@ -71,4 +71,4 @@ subprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eclipse.project.name = 'NetworkedTicTacToe' + '-parent'
|
eclipse.project.name = 'uitest' + '-parent'
|
||||||
@@ -7,7 +7,6 @@ dependencies {
|
|||||||
api "com.badlogicgames.gdx:gdx-box2d:$gdxVersion"
|
api "com.badlogicgames.gdx:gdx-box2d:$gdxVersion"
|
||||||
api "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
|
api "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
|
||||||
api "com.badlogicgames.gdx:gdx:$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-actors:$ktxVersion"
|
||||||
api "io.github.libktx:ktx-ai:$ktxVersion"
|
api "io.github.libktx:ktx-ai:$ktxVersion"
|
||||||
api "io.github.libktx:ktx-app:$ktxVersion"
|
api "io.github.libktx:ktx-app:$ktxVersion"
|
||||||
@@ -41,3 +40,7 @@ dependencies {
|
|||||||
implementation "io.github.berstanio:gdx-svmhelper-annotations:$graalHelperVersion"
|
implementation "io.github.berstanio:gdx-svmhelper-annotations:$graalHelperVersion"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
||||||
75
uitest/core/src/main/kotlin/com/iofferyoutea/uitest/Main.kt
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package com.iofferyoutea.uitest
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.graphics.Texture
|
||||||
|
import com.badlogic.gdx.graphics.g2d.BitmapFont
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.Stage
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Container
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Label
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable
|
||||||
|
import ktx.app.KtxGame
|
||||||
|
import ktx.app.KtxScreen
|
||||||
|
import ktx.app.clearScreen
|
||||||
|
import ktx.async.KtxAsync
|
||||||
|
import org.w3c.dom.Text
|
||||||
|
|
||||||
|
class Main : KtxGame<KtxScreen>() {
|
||||||
|
override fun create() {
|
||||||
|
KtxAsync.initiate()
|
||||||
|
|
||||||
|
addScreen(FirstScreen())
|
||||||
|
setScreen<FirstScreen>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FirstScreen : KtxScreen {
|
||||||
|
val stage = Stage()
|
||||||
|
val table = Table()
|
||||||
|
|
||||||
|
//region Label
|
||||||
|
val labelStyle = Label.LabelStyle(
|
||||||
|
BitmapFont(),
|
||||||
|
Color.BLACK
|
||||||
|
)
|
||||||
|
val label = Label("My Label", labelStyle)
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
//region TextButton
|
||||||
|
val textButtonStyle = TextButton.TextButtonStyle(
|
||||||
|
TextureRegionDrawable(Texture("logo.png")),
|
||||||
|
TextureRegionDrawable(Texture("logo.png")),
|
||||||
|
TextureRegionDrawable(Texture("logo.png")),
|
||||||
|
BitmapFont()
|
||||||
|
)
|
||||||
|
val textButton = TextButton("My Text Button", textButtonStyle)
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
override fun show() {
|
||||||
|
stage.addActor(table)
|
||||||
|
|
||||||
|
table.setFillParent(true)
|
||||||
|
table.debug = true
|
||||||
|
|
||||||
|
label.wrap = false
|
||||||
|
label.setFontScale(5f) // Label text size must be done through setFontScale(f). Maybe we can do like this cell's width / 1080->? Or whatever would equal 1 on the dev machine
|
||||||
|
table.add(label)
|
||||||
|
table.getCell<Label>(label).width(1200f).height(400f) // Widget sizes are meant to be controlled through their parent like we do here
|
||||||
|
|
||||||
|
table.row()
|
||||||
|
table.add(textButton)
|
||||||
|
table.getCell<TextButton>(textButton).width(100f).height(600f)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun render(delta: Float) {
|
||||||
|
clearScreen(red = 0.7f, green = 0.7f, blue = 0.7f)
|
||||||
|
|
||||||
|
stage.act()
|
||||||
|
stage.draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
stage.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,15 +14,14 @@ org.gradle.configureondemand=false
|
|||||||
# Documented at: https://docs.gradle.org/current/userguide/command_line_interface.html#sec:command_line_logging
|
# Documented at: https://docs.gradle.org/current/userguide/command_line_interface.html#sec:command_line_logging
|
||||||
org.gradle.logging.level=quiet
|
org.gradle.logging.level=quiet
|
||||||
ktxVersion=1.13.1-rc1
|
ktxVersion=1.13.1-rc1
|
||||||
kotlinVersion=2.2.21
|
kotlinVersion=2.3.0
|
||||||
kotlinxCoroutinesVersion=1.8.1
|
|
||||||
aiVersion=1.8.2
|
|
||||||
artemisOdbVersion=2.3.0
|
artemisOdbVersion=2.3.0
|
||||||
|
aiVersion=1.8.2
|
||||||
ashleyVersion=1.7.4
|
ashleyVersion=1.7.4
|
||||||
visUiVersion=1.5.7
|
kotlinxCoroutinesVersion=1.10.2
|
||||||
|
visUiVersion=1f8b37a24b
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableR8.fullMode=false
|
android.enableR8.fullMode=false
|
||||||
enableGraalNative=false
|
enableGraalNative=false
|
||||||
graalHelperVersion=2.0.1
|
|
||||||
gdxVersion=1.14.0
|
gdxVersion=1.14.0
|
||||||
projectVersion=1.0.0
|
projectVersion=1.0.0
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
#This file is generated by updateDaemonJvm
|
#This file is generated by updateDaemonJvm
|
||||||
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73c462e34475aeb6509ab7ba3eda218f/redirect
|
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect
|
||||||
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/54001d0b636ad500b432d20ef3d580d0/redirect
|
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/93aeea858331bd6bb00ba94759830234/redirect
|
||||||
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73c462e34475aeb6509ab7ba3eda218f/redirect
|
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect
|
||||||
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/54001d0b636ad500b432d20ef3d580d0/redirect
|
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/b41931cf1e70bc8e08d7dd19c343ef00/redirect
|
||||||
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29c55e6bad8a0049163f0184625cecd9/redirect
|
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/3426ffcaa54c3f62406beb1f1ab8b179/redirect
|
||||||
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/3ac7a5361c25c0b23d933f44bdb0abd9/redirect
|
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/d6690dfd71c4c91e08577437b5b2beb0/redirect
|
||||||
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73c462e34475aeb6509ab7ba3eda218f/redirect
|
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect
|
||||||
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/54001d0b636ad500b432d20ef3d580d0/redirect
|
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/b41931cf1e70bc8e08d7dd19c343ef00/redirect
|
||||||
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/28937bb8a7f83f57de92429a9a11c04e/redirect
|
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/1e91f45234d88a64dafb961c93ddc75a/redirect
|
||||||
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/52fa104f4f641439587f75dd68b31bc2/redirect
|
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/552c7bffe0370c66410a51c55985b511/redirect
|
||||||
toolchainVersion=17
|
toolchainVersion=21
|
||||||
BIN
uitest/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0-milestone-1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright © 2015-2021 the original authors.
|
# Copyright © 2015 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -114,7 +114,6 @@ case "$( uname )" in #(
|
|||||||
NONSTOP* ) nonstop=true ;;
|
NONSTOP* ) nonstop=true ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
@@ -172,7 +171,6 @@ fi
|
|||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if "$cygwin" || "$msys" ; then
|
if "$cygwin" || "$msys" ; then
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
@@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
-classpath "$CLASSPATH" \
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
# Stop when "xargs" is not available.
|
||||||
@@ -70,11 +70,10 @@ goto fail
|
|||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
@@ -5,4 +5,4 @@ plugins {
|
|||||||
// A list of which subprojects to load as part of the same larger project.
|
// A list of which subprojects to load as part of the same larger project.
|
||||||
// You can remove Strings from the list and reload the Gradle project
|
// You can remove Strings from the list and reload the Gradle project
|
||||||
// if you want to temporarily disable a subproject.
|
// if you want to temporarily disable a subproject.
|
||||||
include 'android', 'core', 'lwjgl3'
|
include 'android', 'core'
|
||||||