Compare commits
2 Commits
| Author | SHA256 | Date | |
|---|---|---|---|
| 1b07a4e414 | |||
| 212119adac |
|
Before Width: | Height: | Size: 4.6 MiB |
@@ -162,3 +162,42 @@ 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++
|
||||||
256
Exercises/NetworkedTicTacToe/GameServer/GameServer.cpp
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
16
Exercises/NetworkedTicTacToe/GameServer/Makefile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
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,4 +1,4 @@
|
|||||||
# uitest
|
# NetworkedTicTacToe
|
||||||
|
|
||||||
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,6 +7,7 @@ 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
|
||||||
@@ -26,6 +27,8 @@ 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,6 +2,8 @@
|
|||||||
<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"
|
||||||
@@ -12,7 +14,7 @@
|
|||||||
tools:ignore="UnusedAttribute"
|
tools:ignore="UnusedAttribute"
|
||||||
android:theme="@style/GdxTheme">
|
android:theme="@style/GdxTheme">
|
||||||
<activity
|
<activity
|
||||||
android:name="com.iofferyoutea.uitest.android.AndroidLauncher"
|
android:name="u0012604.tictactoe.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 = "com.iofferyoutea.uitest"
|
namespace = "u0012604.tictactoe"
|
||||||
compileSdk = 35
|
compileSdk = 35
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
@@ -31,7 +31,7 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'com.iofferyoutea.uitest'
|
applicationId 'u0012604.tictactoe'
|
||||||
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', 'com.iofferyoutea.uitest/com.iofferyoutea.uitest.android.AndroidLauncher'
|
commandLine "$adb", 'shell', 'am', 'start', '-n', 'u0012604.tictactoe/u0012604.tictactoe.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">uitest</string>
|
<string name="app_name">NetworkedTicTacToe</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package com.iofferyoutea.uitest.android
|
package u0012604.tictactoe.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 com.iofferyoutea.uitest.Main
|
import u0012604.tictactoe.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.9.3"
|
classpath 'com.android.tools.build:gradle:8.13.2'
|
||||||
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 = 'uitest'
|
ext.appName = 'NetworkedTicTacToe'
|
||||||
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 = 'uitest' + '-parent'
|
eclipse.project.name = 'NetworkedTicTacToe' + '-parent'
|
||||||
@@ -7,6 +7,7 @@ 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"
|
||||||
@@ -40,7 +41,3 @@ dependencies {
|
|||||||
implementation "io.github.berstanio:gdx-svmhelper-annotations:$graalHelperVersion"
|
implementation "io.github.berstanio:gdx-svmhelper-annotations:$graalHelperVersion"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
|
||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
|
||||||
}
|
|
||||||
@@ -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,70 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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("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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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,170 @@
|
|||||||
|
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!!
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,14 +14,15 @@ 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.3.0
|
kotlinVersion=2.2.21
|
||||||
artemisOdbVersion=2.3.0
|
kotlinxCoroutinesVersion=1.8.1
|
||||||
aiVersion=1.8.2
|
aiVersion=1.8.2
|
||||||
|
artemisOdbVersion=2.3.0
|
||||||
ashleyVersion=1.7.4
|
ashleyVersion=1.7.4
|
||||||
kotlinxCoroutinesVersion=1.10.2
|
visUiVersion=1.5.7
|
||||||
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/df211d3c3eefdc408b462041881bc575/redirect
|
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73c462e34475aeb6509ab7ba3eda218f/redirect
|
||||||
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/93aeea858331bd6bb00ba94759830234/redirect
|
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/54001d0b636ad500b432d20ef3d580d0/redirect
|
||||||
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect
|
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73c462e34475aeb6509ab7ba3eda218f/redirect
|
||||||
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/b41931cf1e70bc8e08d7dd19c343ef00/redirect
|
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/54001d0b636ad500b432d20ef3d580d0/redirect
|
||||||
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/3426ffcaa54c3f62406beb1f1ab8b179/redirect
|
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29c55e6bad8a0049163f0184625cecd9/redirect
|
||||||
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/d6690dfd71c4c91e08577437b5b2beb0/redirect
|
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/3ac7a5361c25c0b23d933f44bdb0abd9/redirect
|
||||||
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect
|
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73c462e34475aeb6509ab7ba3eda218f/redirect
|
||||||
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/b41931cf1e70bc8e08d7dd19c343ef00/redirect
|
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/54001d0b636ad500b432d20ef3d580d0/redirect
|
||||||
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/1e91f45234d88a64dafb961c93ddc75a/redirect
|
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/28937bb8a7f83f57de92429a9a11c04e/redirect
|
||||||
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/552c7bffe0370c66410a51c55985b511/redirect
|
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/52fa104f4f641439587f75dd68b31bc2/redirect
|
||||||
toolchainVersion=21
|
toolchainVersion=17
|
||||||
BIN
Exercises/NetworkedTicTacToe/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.4.0-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0-milestone-1-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 the original authors.
|
# Copyright © 2015-2021 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/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/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,6 +114,7 @@ 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.
|
||||||
@@ -171,6 +172,7 @@ 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" )
|
||||||
|
|
||||||
@@ -210,7 +212,8 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
# Stop when "xargs" is not available.
|
||||||
@@ -70,10 +70,11 @@ 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%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
185
Exercises/NetworkedTicTacToe/lwjgl3/build.gradle
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
BIN
Exercises/NetworkedTicTacToe/lwjgl3/icons/logo.icns
Normal file
BIN
Exercises/NetworkedTicTacToe/lwjgl3/icons/logo.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
Exercises/NetworkedTicTacToe/lwjgl3/icons/logo.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
54
Exercises/NetworkedTicTacToe/lwjgl3/nativeimage.gradle
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
|
||||||
|
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":[]
|
||||||
|
}"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
@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)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 806 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
@@ -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'
|
include 'android', 'core', 'lwjgl3'
|
||||||
@@ -2,7 +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" />
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:fullBackupContent="true"
|
android:fullBackupContent="true"
|
||||||
@@ -15,7 +14,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="portrait"
|
android:screenOrientation="landscape"
|
||||||
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.
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 94 B |
|
Before Width: | Height: | Size: 108 B |
|
Before Width: | Height: | Size: 197 B |
|
Before Width: | Height: | Size: 216 B |
|
Before Width: | Height: | Size: 211 B |
|
Before Width: | Height: | Size: 154 B |
|
Before Width: | Height: | Size: 129 B |
|
Before Width: | Height: | Size: 81 B |
@@ -7,6 +7,7 @@ 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"
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
package com.iofferyoutea.WitchQueen
|
|
||||||
|
|
||||||
class Game(val clients: Array<Client>) {
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
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
|
||||||
@@ -11,14 +10,13 @@ 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(MainMenu())
|
addScreen(FirstScreen())
|
||||||
setScreen<MainMenu>()
|
setScreen<FirstScreen>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.iofferyoutea.WitchQueen
|
|
||||||
|
|
||||||
enum class MapType {
|
|
||||||
DUNGEON, STREETS
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.iofferyoutea.WitchQueen
|
|
||||||
|
|
||||||
class PlayerProfile {
|
|
||||||
var iconPath = "default.png"
|
|
||||||
var username = "Default"
|
|
||||||
val availableItems: MutableList<Int> = mutableListOf() // Use Item IDs?
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
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!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||