Compare commits
2 Commits
fe79c271fa
...
main
| Author | SHA256 | Date | |
|---|---|---|---|
| 1b07a4e414 | |||
| 212119adac |
18
Exercises/NetworkedTicTacToe/.editorconfig
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# https://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.{java,scala,groovy,kt,kts}]
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.gradle]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
2
Exercises/NetworkedTicTacToe/.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
|
*.bat text=auto eol=crlf
|
||||||
203
Exercises/NetworkedTicTacToe/.gitignore
vendored
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
## Gradle:
|
||||||
|
.gradle/
|
||||||
|
gradle-app.setting
|
||||||
|
/build/
|
||||||
|
/android/build/
|
||||||
|
/core/build/
|
||||||
|
/lwjgl2/build/
|
||||||
|
/lwjgl3/build/
|
||||||
|
/html/build/
|
||||||
|
/teavm/build/
|
||||||
|
/ios/build/
|
||||||
|
/ios-moe/build/
|
||||||
|
/headless/build/
|
||||||
|
/server/build/
|
||||||
|
/shared/build/
|
||||||
|
|
||||||
|
## Java:
|
||||||
|
*.class
|
||||||
|
*.war
|
||||||
|
*.ear
|
||||||
|
hs_err_pid*
|
||||||
|
.attach_pid*
|
||||||
|
|
||||||
|
## Android:
|
||||||
|
/android/libs/armeabi-v7a/
|
||||||
|
/android/libs/arm64-v8a/
|
||||||
|
/android/libs/x86/
|
||||||
|
/android/libs/x86_64/
|
||||||
|
/android/gen/
|
||||||
|
/android/out/
|
||||||
|
local.properties
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
|
||||||
|
## Robovm:
|
||||||
|
/ios/robovm-build/
|
||||||
|
|
||||||
|
## iOS:
|
||||||
|
/ios/xcode/*.xcodeproj/*
|
||||||
|
!/ios/xcode/*.xcodeproj/xcshareddata
|
||||||
|
!/ios/xcode/*.xcodeproj/project.pbxproj
|
||||||
|
/ios/xcode/native/
|
||||||
|
/ios/IOSLauncher.app
|
||||||
|
/ios/IOSLauncher.app.dSYM
|
||||||
|
|
||||||
|
## GWT:
|
||||||
|
/html/war/
|
||||||
|
/html/gwt-unitCache/
|
||||||
|
.apt_generated/
|
||||||
|
/html/war/WEB-INF/deploy/
|
||||||
|
/html/war/WEB-INF/classes/
|
||||||
|
.gwt/
|
||||||
|
gwt-unitCache/
|
||||||
|
www-test/
|
||||||
|
.gwt-tmp/
|
||||||
|
|
||||||
|
## TeaVM:
|
||||||
|
# Not sure yet...
|
||||||
|
|
||||||
|
## IntelliJ, Android Studio:
|
||||||
|
.idea/
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
## Eclipse:
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
.metadata/
|
||||||
|
/android/bin/
|
||||||
|
/core/bin/
|
||||||
|
/lwjgl2/bin/
|
||||||
|
/lwjgl3/bin/
|
||||||
|
/html/bin/
|
||||||
|
/teavm/bin/
|
||||||
|
/ios/bin/
|
||||||
|
/ios-moe/bin/
|
||||||
|
/headless/bin/
|
||||||
|
/server/bin/
|
||||||
|
/shared/bin/
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
*.swp
|
||||||
|
*~.nib
|
||||||
|
.settings/
|
||||||
|
.loadpath
|
||||||
|
.externalToolBuilders/
|
||||||
|
*.launch
|
||||||
|
|
||||||
|
|
||||||
|
## NetBeans:
|
||||||
|
|
||||||
|
/nbproject/private/
|
||||||
|
/android/nbproject/private/
|
||||||
|
/core/nbproject/private/
|
||||||
|
/lwjgl2/nbproject/private/
|
||||||
|
/lwjgl3/nbproject/private/
|
||||||
|
/html/nbproject/private/
|
||||||
|
/teavm/nbproject/private/
|
||||||
|
/ios/nbproject/private/
|
||||||
|
/ios-moe/nbproject/private/
|
||||||
|
/headless/nbproject/private/
|
||||||
|
/server/nbproject/private/
|
||||||
|
/shared/nbproject/private/
|
||||||
|
|
||||||
|
/nbbuild/
|
||||||
|
/android/nbbuild/
|
||||||
|
/core/nbbuild/
|
||||||
|
/lwjgl2/nbbuild/
|
||||||
|
/lwjgl3/nbbuild/
|
||||||
|
/html/nbbuild/
|
||||||
|
/teavm/nbbuild/
|
||||||
|
/ios/nbbuild/
|
||||||
|
/ios-moe/nbbuild/
|
||||||
|
/headless/nbbuild/
|
||||||
|
/server/nbbuild/
|
||||||
|
/shared/nbbuild/
|
||||||
|
|
||||||
|
/dist/
|
||||||
|
/android/dist/
|
||||||
|
/core/dist/
|
||||||
|
/lwjgl2/dist/
|
||||||
|
/lwjgl3/dist/
|
||||||
|
/html/dist/
|
||||||
|
/teavm/dist/
|
||||||
|
/ios/dist/
|
||||||
|
/ios-moe/dist/
|
||||||
|
/headless/dist/
|
||||||
|
/server/dist/
|
||||||
|
/shared/dist/
|
||||||
|
|
||||||
|
/nbdist/
|
||||||
|
/android/nbdist/
|
||||||
|
/core/nbdist/
|
||||||
|
/lwjgl2/nbdist/
|
||||||
|
/lwjgl3/nbdist/
|
||||||
|
/html/nbdist/
|
||||||
|
/teavm/nbdist/
|
||||||
|
/ios/nbdist/
|
||||||
|
/ios-moe/nbdist/
|
||||||
|
/headless/nbdist/
|
||||||
|
/server/nbdist/
|
||||||
|
/shared/nbdist/
|
||||||
|
|
||||||
|
nbactions.xml
|
||||||
|
nb-configuration.xml
|
||||||
|
|
||||||
|
## OS-Specific:
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
## Miscellaneous:
|
||||||
|
*~
|
||||||
|
*.*#
|
||||||
|
*#*#
|
||||||
|
/.kotlin/
|
||||||
|
/assets/assets.txt
|
||||||
|
|
||||||
|
## Special cases:
|
||||||
|
|
||||||
|
## There is a resource-config.json file generated by nativeimage.gradle if you use Graal Native Image.
|
||||||
|
## Some usage may need extra resource configuration in a different file with the same name.
|
||||||
|
## 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 .
|
||||||
|
**/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
|
||||||
35
Exercises/NetworkedTicTacToe/README.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# NetworkedTicTacToe
|
||||||
|
|
||||||
|
A [libGDX](https://libgdx.com/) project generated with [gdx-liftoff](https://github.com/libgdx/gdx-liftoff).
|
||||||
|
|
||||||
|
This project was generated with a Kotlin project template that includes Kotlin application launchers and [KTX](https://libktx.github.io/) utilities.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## Gradle
|
||||||
|
|
||||||
|
This project uses [Gradle](https://gradle.org/) to manage dependencies.
|
||||||
|
The Gradle wrapper was included, so you can run Gradle tasks using `gradlew.bat` or `./gradlew` commands.
|
||||||
|
Useful Gradle tasks and flags:
|
||||||
|
|
||||||
|
- `--continue`: when using this flag, errors will not stop the tasks from running.
|
||||||
|
- `--daemon`: thanks to this flag, Gradle daemon will be used to run chosen tasks.
|
||||||
|
- `--offline`: when using this flag, cached dependency archives will be used.
|
||||||
|
- `--refresh-dependencies`: this flag forces validation of all dependencies. Useful for snapshot versions.
|
||||||
|
- `android:lint`: performs Android project validation.
|
||||||
|
- `build`: builds sources and archives of every project.
|
||||||
|
- `cleanEclipse`: removes Eclipse project data.
|
||||||
|
- `cleanIdea`: removes IntelliJ project data.
|
||||||
|
- `clean`: removes `build` folders, which store compiled classes and built archives.
|
||||||
|
- `eclipse`: generates Eclipse 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).
|
||||||
|
|
||||||
|
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.
|
||||||
|
For example, `core:clean` removes `build` folder only from the `core` project.
|
||||||
29
Exercises/NetworkedTicTacToe/android/AndroidManifest.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<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
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:fullBackupContent="true"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:isGame="true"
|
||||||
|
android:appCategory="game"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
tools:ignore="UnusedAttribute"
|
||||||
|
android:theme="@style/GdxTheme">
|
||||||
|
<activity
|
||||||
|
android:name="u0012604.tictactoe.android.AndroidLauncher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:screenOrientation="landscape"
|
||||||
|
android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|screenLayout"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
136
Exercises/NetworkedTicTacToe/android/build.gradle
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "u0012604.tictactoe"
|
||||||
|
compileSdk = 35
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
manifest.srcFile 'AndroidManifest.xml'
|
||||||
|
java.setSrcDirs(['src/main/java', 'src/main/kotlin'])
|
||||||
|
aidl.setSrcDirs(['src/main/java', 'src/main/kotlin'])
|
||||||
|
renderscript.setSrcDirs(['src/main/java', 'src/main/kotlin'])
|
||||||
|
res.setSrcDirs(['res'])
|
||||||
|
assets.setSrcDirs(['../assets'])
|
||||||
|
jniLibs.setSrcDirs(['libs'])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
packagingOptions {
|
||||||
|
resources {
|
||||||
|
excludes += ['META-INF/robovm/ios/robovm.xml', 'META-INF/DEPENDENCIES.txt', 'META-INF/DEPENDENCIES',
|
||||||
|
'META-INF/dependencies.txt', '**/*.gwt.xml']
|
||||||
|
pickFirsts += ['META-INF/LICENSE.txt', 'META-INF/LICENSE', 'META-INF/license.txt', 'META-INF/LGPL2.1',
|
||||||
|
'META-INF/NOTICE.txt', 'META-INF/NOTICE', 'META-INF/notice.txt']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defaultConfig {
|
||||||
|
applicationId 'u0012604.tictactoe'
|
||||||
|
minSdkVersion 21
|
||||||
|
targetSdkVersion 35
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility "8"
|
||||||
|
targetCompatibility "8"
|
||||||
|
coreLibraryDesugaringEnabled = true
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin.compilerOptions.jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
// needed for AAPT2, may be needed for other tools
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations { natives }
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
|
||||||
|
implementation "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion"
|
||||||
|
implementation project(':core')
|
||||||
|
|
||||||
|
natives "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-arm64-v8a"
|
||||||
|
natives "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-armeabi-v7a"
|
||||||
|
natives "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-x86"
|
||||||
|
natives "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-x86_64"
|
||||||
|
natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-arm64-v8a"
|
||||||
|
natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-armeabi-v7a"
|
||||||
|
natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86"
|
||||||
|
natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86_64"
|
||||||
|
natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-arm64-v8a"
|
||||||
|
natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi-v7a"
|
||||||
|
natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86"
|
||||||
|
natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86_64"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called every time gradle gets executed, takes the native dependencies of
|
||||||
|
// the natives configuration, and extracts them to the proper libs/ folders
|
||||||
|
// so they get packed with the APK.
|
||||||
|
tasks.register('copyAndroidNatives') {
|
||||||
|
doFirst {
|
||||||
|
file("libs/armeabi-v7a/").mkdirs()
|
||||||
|
file("libs/arm64-v8a/").mkdirs()
|
||||||
|
file("libs/x86_64/").mkdirs()
|
||||||
|
file("libs/x86/").mkdirs()
|
||||||
|
|
||||||
|
configurations.natives.copy().files.each { jar ->
|
||||||
|
def outputDir = null
|
||||||
|
if(jar.name.endsWith("natives-armeabi-v7a.jar")) outputDir = file("libs/armeabi-v7a")
|
||||||
|
if(jar.name.endsWith("natives-arm64-v8a.jar")) outputDir = file("libs/arm64-v8a")
|
||||||
|
if(jar.name.endsWith("natives-x86_64.jar")) outputDir = file("libs/x86_64")
|
||||||
|
if(jar.name.endsWith("natives-x86.jar")) outputDir = file("libs/x86")
|
||||||
|
if(outputDir != null) {
|
||||||
|
copy {
|
||||||
|
from zipTree(jar)
|
||||||
|
into outputDir
|
||||||
|
include "*.so"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.matching { it.name.contains("merge") && it.name.contains("JniLibFolders") }.configureEach { packageTask ->
|
||||||
|
packageTask.dependsOn 'copyAndroidNatives'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('run', Exec) {
|
||||||
|
def path
|
||||||
|
def localProperties = project.file("../local.properties")
|
||||||
|
if (localProperties.exists()) {
|
||||||
|
Properties properties = new Properties()
|
||||||
|
localProperties.withInputStream { instr ->
|
||||||
|
properties.load(instr)
|
||||||
|
}
|
||||||
|
def sdkDir = properties.getProperty('sdk.dir')
|
||||||
|
if (sdkDir) {
|
||||||
|
path = sdkDir
|
||||||
|
} else {
|
||||||
|
path = "$System.env.ANDROID_SDK_ROOT"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
path = "$System.env.ANDROID_SDK_ROOT"
|
||||||
|
}
|
||||||
|
|
||||||
|
def adb = path + "/platform-tools/adb"
|
||||||
|
commandLine "$adb", 'shell', 'am', 'start', '-n', 'u0012604.tictactoe/u0012604.tictactoe.android.AndroidLauncher'
|
||||||
|
}
|
||||||
|
|
||||||
|
eclipse.project.name = appName + "-android"
|
||||||
BIN
Exercises/NetworkedTicTacToe/android/ic_launcher-web.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
52
Exercises/NetworkedTicTacToe/android/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# To enable ProGuard in your project, edit project.properties
|
||||||
|
# to define the proguard.config property as described in that file.
|
||||||
|
#
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the ProGuard
|
||||||
|
# include property in project.properties.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# https://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
-verbose
|
||||||
|
|
||||||
|
-dontwarn android.support.**
|
||||||
|
-dontwarn com.badlogic.gdx.backends.android.AndroidFragmentApplication
|
||||||
|
|
||||||
|
# Needed by the gdx-controllers official extension.
|
||||||
|
-keep class com.badlogic.gdx.controllers.android.AndroidControllers
|
||||||
|
|
||||||
|
# Needed by the Box2D official extension.
|
||||||
|
-keepclassmembers class com.badlogic.gdx.physics.box2d.World {
|
||||||
|
boolean contactFilter(long, long);
|
||||||
|
boolean getUseDefaultContactFilter();
|
||||||
|
void beginContact(long);
|
||||||
|
void endContact(long);
|
||||||
|
void preSolve(long, long);
|
||||||
|
void postSolve(long, long);
|
||||||
|
boolean reportFixture(long);
|
||||||
|
float reportRayFixture(long, float, float, float, float, float);
|
||||||
|
}
|
||||||
|
|
||||||
|
# You will need the next three lines if you use scene2d for UI or gameplay.
|
||||||
|
# If you don't use scene2d at all, you can remove or comment out the next line:
|
||||||
|
-keep public class com.badlogic.gdx.scenes.scene2d.** { *; }
|
||||||
|
# You will need the next two lines if you use BitmapFont or any scene2d.ui text:
|
||||||
|
-keep public class com.badlogic.gdx.graphics.g2d.BitmapFont { *; }
|
||||||
|
# You will probably need this line in most cases:
|
||||||
|
-keep public class com.badlogic.gdx.graphics.Color { *; }
|
||||||
|
|
||||||
|
# These two lines are used with mapping files; see https://developer.android.com/build/shrink-code#retracing
|
||||||
|
-keepattributes LineNumberTable,SourceFile
|
||||||
|
-renamesourcefileattribute SourceFile
|
||||||
14
Exercises/NetworkedTicTacToe/android/project.properties
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# This file is automatically generated by Android Tools.
|
||||||
|
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||||
|
#
|
||||||
|
# This file must be checked in Version Control Systems.
|
||||||
|
#
|
||||||
|
# To customize properties used by the Ant build system edit
|
||||||
|
# "ant.properties", and override values to adapt the script to your
|
||||||
|
# project structure.
|
||||||
|
#
|
||||||
|
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||||
|
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-rules.pro
|
||||||
|
|
||||||
|
# Project target.
|
||||||
|
target=android-21
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_background_color"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="512dp"
|
||||||
|
android:height="512dp"
|
||||||
|
android:viewportWidth="512"
|
||||||
|
android:viewportHeight="512">
|
||||||
|
<path
|
||||||
|
android:pathData="M457.78,372.5l-201.78,116.5l-201.78,-116.5l0,-233l201.78,-116.5l201.78,116.5z"
|
||||||
|
android:strokeWidth="23"
|
||||||
|
android:fillColor="#4a4a4a"
|
||||||
|
android:strokeColor="#000000"/>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M449.71,363.84l-193.71,111.84l-193.71,-111.84l0,-223.68l193.71,-111.84l193.71,111.84z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M256,67.33c-0.36,0.13 -1.07,0.4 -2,0.99 -1.67,1.07 -3.94,2.89 -6.4,5.26 -4.93,4.75 -10.72,11.66 -16.1,19.34a142.77,142.77 0,0 0,-2.95 4.41c9.43,-1.86 18.44,-2.8 27.45,-2.8 9.01,0 18.03,0.94 27.45,2.8a142.77,142.77 135,0 0,-2.95 -4.41c-5.38,-7.68 -11.17,-14.6 -16.1,-19.34 -2.46,-2.37 -4.73,-4.2 -6.4,-5.26a9.93,9.93 135,0 0,-2 -0.99zM256,108.66c-11.75,0 -23.5,1.9 -36.98,5.69C214.74,123.36 212,131.66 212,136.8l0,127.71c4.08,-13.81 7.7,-30.59 11.2,-51.52L223.2,200.8c0,-8.8 3.65,-18.74 9.03,-27.7 2.69,-4.48 5.85,-8.63 9.66,-11.94C245.69,157.86 250.4,155.2 256,155.2s10.31,2.66 14.12,5.96c3.81,3.3 6.97,7.45 9.66,11.94C285.15,182.06 288.8,192 288.8,200.8l0,12.19c3.5,20.94 7.12,37.71 11.2,51.52L300,136.8c0,-5.14 -2.74,-13.44 -7.02,-22.45 -13.47,-3.79 -25.23,-5.69 -36.98,-5.69zM185.6,150.71c-5.21,9.21 -12,23.48 -12,37.29l0,31.2l24,0l0,-31.2c0,-13.81 -6.79,-28.08 -12,-37.29zM326.4,150.71c-5.21,9.21 -12,23.48 -12,37.29l0,31.2l24,0l0,-31.2c0,-13.81 -6.79,-28.08 -12,-37.29zM256,169.6c-0.8,0 -2.49,0.54 -4.68,2.44 -2.19,1.9 -4.63,4.95 -6.74,8.46C240.35,187.54 237.6,196.8 237.6,200.8l0,131.02l-2.16,2.12c-8.49,8.33 -10.26,28.91 -10.52,42.22 5.34,2.83 14.26,5.13 23.88,5.94L248.8,341.6l14.4,0l0,40.49c9.62,-0.8 18.54,-3.11 23.88,-5.94 -0.25,-13.27 -1.97,-33.66 -10.57,-42.26L274.4,331.78L274.4,200.8c0,-4 -2.75,-13.26 -6.97,-20.3 -2.11,-3.52 -4.55,-6.57 -6.74,-8.46C258.49,170.14 256.8,169.6 256,169.6zM243.2,193.6l25.6,0l0,14.4l-25.6,0l0,-14.4zM173.6,233.6l0,93.05c9.94,-8.52 17.6,-17.06 24,-27.75L197.6,233.6l-24,0zM314.4,233.6l0,65.3c6.4,10.69 14.06,19.24 24,27.75L338.4,233.6l-24,0zM223.2,276.78c-2.81,8.32 -5.87,15.8 -9.36,22.54 -7.44,14.41 -16.73,25.6 -28.37,36.03l30.94,1.55c1.73,-3.88 3.94,-7.57 6.78,-10.87l0,-49.24zM288.8,276.78l0,49.28c2.81,3.3 5,6.98 6.72,10.84l31,-1.55c-11.64,-10.42 -20.92,-21.62 -28.37,-36.03 -3.48,-6.74 -6.55,-14.22 -9.36,-22.54zM446.4,289.61c-10.84,4.52 -15.22,15.65 -21.56,33.46 0,0 -36.65,0.34 -42.7,14.05 -6.46,14.65 19.67,43.83 19.67,43.83s-21.71,-6.98 -30.34,-1.69c-9.59,5.88 -13.61,30.87 -13.61,30.87 -2.83,-0.68 -5.52,-1.24 -8.11,-1.71L341.33,383.2l8.53,0l-4.79,-8.77 -42.81,2.14 -0.65,1.2c-0,0.73 -0.01,1.56 -0.01,2.22l0,3.2l5.6,0l-8.53,25.6 17.07,-12.8 4,12.01c-11.83,2.51 -20.44,8.55 -28.72,16.43L281.6,405.6l12.8,0l-10.24,-12.49C275.61,395.79 265.83,396.8 256,396.8c-9.85,0 -19.66,-1.02 -28.22,-3.7L217.6,405.6l12.8,0l-10.31,20.62c-9.71,-4.02 -20.02,-8.02 -30.08,-11.44L196.27,396l17.07,12.8L204.8,383.2l5.6,0l0,-3.2c0,-0.65 -0.01,-1.47 -0.01,-2.18l-0.68,-1.24 -42.82,-2.14 -4.76,8.77l8.53,0l-7.96,23.89c-3.19,-0.65 -6.24,-1.16 -9.1,-1.49 -25.25,-2.9 -31.78,3.71 -46.35,9.93 0,0 13.33,-77.26 -13.35,-94.7 -3.3,-2.16 -7.43,-3.03 -11.99,-2.98 -5.06,0.04 -10.67,1.22 -16.31,2.99L65.6,442.4l189.6,0l0.8,1.6 12.8,-25.6 18.79,9.4c-4.45,4.5 -8.93,9.46 -13.88,14.6L446.4,442.4L446.4,289.61zM168.82,348.93c-4.32,3.9 -5.52,7.7 -5.45,9.24 0.04,0.88 0.19,1 0.41,1.19 0.22,0.19 0.81,0.63 2.63,0.63l0.18,0l44.16,2.21c0.25,-3.58 0.68,-7.33 1.39,-11.11l-43.31,-2.17zM343.18,348.93l-43.35,2.17c0.72,3.78 1.15,7.53 1.41,11.11l44.18,-2.21L345.6,360c1.82,0 2.42,-0.44 2.63,-0.63 0.22,-0.19 0.37,-0.32 0.41,-1.19 0.06,-1.54 -1.14,-5.34 -5.45,-9.24zM179.2,396l5.7,17.09c-7.29,-2.35 -14.35,-4.32 -20.86,-5.72L179.2,396zM332.8,396l16.44,12.33c-7.58,-1.34 -14.17,-1.78 -20.05,-1.5L332.8,396zM243.2,418.4l11.64,23.27c-7.9,-3.74 -18.63,-8.66 -30.68,-13.75L243.2,418.4z"
|
||||||
|
android:fillColor="#ffda54"/>
|
||||||
|
</group>
|
||||||
|
<path
|
||||||
|
android:pathData="M135.76,190.05L112.46,190.05v-15.61q0,-6.81 -0.77,-8.47 -0.72,-1.72 -3.26,-1.72 -2.88,0 -3.65,2.05 -0.77,2.05 -0.77,8.85v41.61q0,6.53 0.77,8.52 0.77,1.99 3.49,1.99 2.6,0 3.38,-1.99 0.83,-1.99 0.83,-9.35v-11.23h23.3v3.49q0,13.89 -1.99,19.7 -1.94,5.81 -8.69,10.18 -6.7,4.37 -16.55,4.37 -10.24,0 -16.88,-3.71 -6.64,-3.71 -8.8,-10.24 -2.16,-6.59 -2.16,-19.76L80.7,182.58q0,-9.68 0.66,-14.5 0.66,-4.87 3.93,-9.35 3.32,-4.48 9.13,-7.03 5.87,-2.6 13.45,-2.6 10.29,0 16.99,3.98 6.7,3.98 8.8,9.96 2.1,5.92 2.1,18.48zM197.68,150.98v89.59h-23.3v-37.63h-6.97v37.63h-23.3v-89.59h23.3v32.04h6.97v-32.04zM247.27,150.98 L260.6,240.57L236.75,240.57l-1.16,-16.1h-8.36l-1.38,16.1h-24.13l11.84,-89.59zM234.93,208.59q-1.77,-15.22 -3.54,-37.57 -3.54,25.68 -4.43,37.57zM316.6,150.98v89.59h-20.42l-12.12,-40.73v40.73h-19.48v-89.59h19.48l13.06,40.34v-40.34zM379.41,183.96h-23.3v-8.13q0,-7.69 -0.66,-9.63 -0.66,-1.94 -3.15,-1.94 -2.16,0 -2.93,1.66 -0.77,1.66 -0.77,8.52v43q0,6.03 0.77,7.97 0.77,1.88 3.1,1.88 2.55,0 3.43,-2.16 0.94,-2.16 0.94,-8.41v-10.62h-4.7v-13.61h27.28v48.09h-14.66l-2.16,-6.42q-2.38,4.15 -6.03,6.25 -3.6,2.05 -8.52,2.05 -5.87,0 -11.01,-2.82 -5.09,-2.88 -7.75,-7.08 -2.66,-4.21 -3.32,-8.8 -0.66,-4.65 -0.66,-13.89v-26.62q0,-12.84 1.38,-18.65 1.38,-5.81 7.91,-10.62 6.59,-4.87 16.99,-4.87 10.24,0 16.99,4.21 6.75,4.21 8.8,10.02 2.05,5.76 2.05,16.77zM388.43,150.98h38.85v17.93h-15.55v16.99h14.55v17.04h-14.55v19.7h17.1v17.93L388.43,240.57Z"
|
||||||
|
android:strokeWidth="6.1"
|
||||||
|
android:fillColor="#f30d0d"
|
||||||
|
android:strokeColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m202.17,286.98v17.93h-13.83v71.66h-23.3v-71.66h-13.78v-17.93zM261.05,286.98v89.59h-23.3v-37.63h-6.97v37.63L207.48,376.57v-89.59h23.3v32.04h6.97v-32.04zM293.69,286.98v89.59h-23.3v-89.59zM352.41,314.09L330.77,314.09v-6.64q0,-4.65 -0.83,-5.92 -0.83,-1.27 -2.77,-1.27 -2.1,0 -3.21,1.72 -1.05,1.72 -1.05,5.2 0,4.48 1.22,6.75 1.16,2.27 6.59,5.48 15.55,9.24 19.59,15.16 4.04,5.92 4.04,19.09 0,9.57 -2.27,14.11 -2.21,4.54 -8.63,7.64 -6.42,3.04 -14.94,3.04 -9.35,0 -15.99,-3.54 -6.59,-3.54 -8.63,-9.02 -2.05,-5.48 -2.05,-15.55v-5.87h21.64v10.9q0,5.04 0.89,6.47 0.94,1.44 3.26,1.44 2.32,0 3.43,-1.83 1.16,-1.83 1.16,-5.42 0,-7.91 -2.16,-10.35 -2.21,-2.43 -10.9,-8.13 -8.69,-5.76 -11.51,-8.36 -2.82,-2.6 -4.7,-7.19 -1.83,-4.59 -1.83,-11.73 0,-10.29 2.6,-15.05 2.66,-4.76 8.52,-7.42 5.87,-2.71 14.17,-2.71 9.08,0 15.44,2.93 6.42,2.93 8.47,7.42 2.1,4.43 2.1,15.11z"
|
||||||
|
android:strokeWidth="6.1"
|
||||||
|
android:fillColor="#f30d0d"
|
||||||
|
android:strokeColor="#000000"/>
|
||||||
|
</vector>
|
||||||
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_background_color">#F5A623FF</color>
|
||||||
|
<color name="background">#FFFFFFFF</color>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">NetworkedTicTacToe</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<resources>
|
||||||
|
<style name="GdxTheme" parent="android:Theme.Material.Light.NoActionBar.Fullscreen">
|
||||||
|
<item name="android:colorBackground">@color/background</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package u0012604.tictactoe.android
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
|
||||||
|
import com.badlogic.gdx.backends.android.AndroidApplication
|
||||||
|
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration
|
||||||
|
import u0012604.tictactoe.Main
|
||||||
|
|
||||||
|
/** Launches the Android application. */
|
||||||
|
class AndroidLauncher : AndroidApplication() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
initialize(Main(), AndroidApplicationConfiguration().apply {
|
||||||
|
// Configure your application here.
|
||||||
|
useImmersiveMode = true // Recommended, but not required.
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Exercises/NetworkedTicTacToe/assets/logo.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
74
Exercises/NetworkedTicTacToe/build.gradle
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
mavenLocal()
|
||||||
|
google()
|
||||||
|
maven { url = 'https://central.sonatype.com/repository/maven-snapshots/' }
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:8.13.2'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
apply plugin: 'eclipse'
|
||||||
|
apply plugin: 'idea'
|
||||||
|
|
||||||
|
// This allows you to "Build and run using IntelliJ IDEA", an option in IDEA's Settings.
|
||||||
|
idea {
|
||||||
|
module {
|
||||||
|
outputDir = file('build/classes/java/main')
|
||||||
|
testOutputDir = file('build/classes/java/test')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configure(subprojects - project(':android')) {
|
||||||
|
apply plugin: 'java-library'
|
||||||
|
apply plugin: 'kotlin'
|
||||||
|
java.sourceCompatibility = 8
|
||||||
|
|
||||||
|
// From https://lyze.dev/2021/04/29/libGDX-Internal-Assets-List/
|
||||||
|
// The article can be helpful when using assets.txt in your project.
|
||||||
|
tasks.register('generateAssetList') {
|
||||||
|
inputs.dir("${project.rootDir}/assets/")
|
||||||
|
// projectFolder/assets
|
||||||
|
File assetsFolder = new File("${project.rootDir}/assets/")
|
||||||
|
// projectFolder/assets/assets.txt
|
||||||
|
File assetsFile = new File(assetsFolder, "assets.txt")
|
||||||
|
// delete that file in case we've already created it
|
||||||
|
assetsFile.delete()
|
||||||
|
|
||||||
|
// iterate through all files inside that folder
|
||||||
|
// convert it to a relative path
|
||||||
|
// and append it to the file assets.txt
|
||||||
|
fileTree(assetsFolder).collect { assetsFolder.relativePath(it) }.sort().each {
|
||||||
|
assetsFile.append(it + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processResources.dependsOn 'generateAssetList'
|
||||||
|
|
||||||
|
compileJava {
|
||||||
|
options.incremental = true
|
||||||
|
}
|
||||||
|
compileKotlin.compilerOptions.jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8)
|
||||||
|
compileTestKotlin.compilerOptions.jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
version = "$projectVersion"
|
||||||
|
ext.appName = 'NetworkedTicTacToe'
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
// You may want to remove the following line if you have errors downloading dependencies.
|
||||||
|
mavenLocal()
|
||||||
|
maven { url = 'https://central.sonatype.com/repository/maven-snapshots/' }
|
||||||
|
maven { url = 'https://jitpack.io' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eclipse.project.name = 'NetworkedTicTacToe' + '-parent'
|
||||||
43
Exercises/NetworkedTicTacToe/core/build.gradle
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
|
||||||
|
eclipse.project.name = appName + '-core'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api "com.badlogicgames.ashley:ashley:$ashleyVersion"
|
||||||
|
api "com.badlogicgames.gdx:gdx-ai:$aiVersion"
|
||||||
|
api "com.badlogicgames.gdx:gdx-box2d:$gdxVersion"
|
||||||
|
api "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
|
||||||
|
api "com.badlogicgames.gdx:gdx:$gdxVersion"
|
||||||
|
api "com.kotcrab.vis:vis-ui:$visUiVersion"
|
||||||
|
api "io.github.libktx:ktx-actors:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-ai:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-app:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-artemis:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-ashley:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-assets-async:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-assets:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-async:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-box2d:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-collections:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-freetype-async:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-freetype:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-graphics:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-i18n:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-inject:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-json:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-log:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-math:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-preferences:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-reflect:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-scene2d:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-style:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-tiled:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-vis-style:$ktxVersion"
|
||||||
|
api "io.github.libktx:ktx-vis:$ktxVersion"
|
||||||
|
api "net.onedaybeard.artemis:artemis-odb:$artemisOdbVersion"
|
||||||
|
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||||
|
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion"
|
||||||
|
|
||||||
|
if(enableGraalNative == 'true') {
|
||||||
|
implementation "io.github.berstanio:gdx-svmhelper-annotations:$graalHelperVersion"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package u0012604.tictactoe
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Gdx
|
||||||
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.graphics.g2d.Batch
|
||||||
|
import com.badlogic.gdx.graphics.glutils.ShapeRenderer
|
||||||
|
import com.badlogic.gdx.math.Vector2
|
||||||
|
import com.badlogic.gdx.utils.Disposable
|
||||||
|
import com.badlogic.gdx.utils.viewport.Viewport
|
||||||
|
import ktx.assets.disposeSafely
|
||||||
|
import ktx.graphics.use
|
||||||
|
import u0012604.tictactoe.screens.FirstScreen.Companion.NOUGHT_RADIUS
|
||||||
|
|
||||||
|
class Board(val viewport: Viewport) : Disposable {
|
||||||
|
private var thirdOfWidth = 0f
|
||||||
|
private var thirdOfHeight = 0f
|
||||||
|
|
||||||
|
private var halfCellW = 0f
|
||||||
|
private var halfCellH = 0f
|
||||||
|
|
||||||
|
private val shapeRenderer = ShapeRenderer()
|
||||||
|
|
||||||
|
private var boardLines = emptyArray<Pair<Vector2, Vector2>>()
|
||||||
|
|
||||||
|
private val board = arrayOf(
|
||||||
|
arrayOf(Token.EMPTY, Token.EMPTY, Token.EMPTY),
|
||||||
|
arrayOf(Token.EMPTY, Token.EMPTY, Token.EMPTY),
|
||||||
|
arrayOf(Token.EMPTY, Token.EMPTY, Token.EMPTY)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun placeToken(row: Int, col: Int, token: Token) =
|
||||||
|
if(row in (0..2) && col in (0 .. 2) && board[row][col] == Token.EMPTY) {
|
||||||
|
board[row][col] = token
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun draw(batch: Batch) {
|
||||||
|
shapeRenderer.use(ShapeRenderer.ShapeType.Filled, viewport.camera.combined) { sr ->
|
||||||
|
|
||||||
|
sr.color = Color.RED
|
||||||
|
|
||||||
|
boardLines.forEach { line ->
|
||||||
|
// Gdx.app.log(TAG, "p0:${line.first}, ${line.second}")
|
||||||
|
|
||||||
|
sr.rectLine(line.first, line.second, 10f)
|
||||||
|
}
|
||||||
|
|
||||||
|
board.forEachIndexed { rowIndex, row ->
|
||||||
|
row.forEachIndexed { colIndex, col ->
|
||||||
|
when(col) {
|
||||||
|
Token.NOUGHT -> drawNought(rowIndex, colIndex, sr)
|
||||||
|
|
||||||
|
Token.CROSS -> drawCross(rowIndex, colIndex, sr)
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resize(width: Int, height: Int) {
|
||||||
|
boardLines = emptyArray()
|
||||||
|
|
||||||
|
thirdOfWidth = width / 3f
|
||||||
|
thirdOfHeight = height / 3f
|
||||||
|
|
||||||
|
// Vertical lines
|
||||||
|
val x1 = thirdOfWidth
|
||||||
|
val x2 = Gdx.graphics.width / 1.5f
|
||||||
|
boardLines += Pair(Vector2(x1, 0f), Vector2(x1, height.toFloat()))
|
||||||
|
boardLines += Pair(Vector2(x2, 0f), Vector2(x2, height.toFloat()))
|
||||||
|
|
||||||
|
// Horizontal lines
|
||||||
|
val y1 = thirdOfHeight
|
||||||
|
val v2 = Gdx.graphics.height.toFloat() / 1.5f
|
||||||
|
|
||||||
|
boardLines += Pair(Vector2(0f, y1), Vector2(width.toFloat(), y1))
|
||||||
|
boardLines += Pair(Vector2(0f, v2), Vector2(width.toFloat(), v2))
|
||||||
|
|
||||||
|
halfCellW = x1 / 2f
|
||||||
|
halfCellH = y1 / 2f
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawNought(row: Int, col: Int, sr: ShapeRenderer) {
|
||||||
|
val x = col * thirdOfWidth + halfCellW
|
||||||
|
val y = (2 - row) * thirdOfHeight + halfCellH
|
||||||
|
|
||||||
|
sr.circle(x, y, NOUGHT_RADIUS)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawCross(row: Int, col: Int, sr: ShapeRenderer) {
|
||||||
|
val flipRow = 2 - row
|
||||||
|
|
||||||
|
val l1x1 = col * thirdOfWidth + 50f
|
||||||
|
val l1y1 = flipRow * thirdOfHeight + 50f
|
||||||
|
val l1x2 = col * thirdOfWidth - 50f + 2 * halfCellW
|
||||||
|
val l1y2 = flipRow * thirdOfHeight - 50f + 2 * halfCellH
|
||||||
|
|
||||||
|
sr.rectLine(l1x1, l1y1, l1x2, l1y2, 10f)
|
||||||
|
sr.rectLine(l1x1, l1y2, l1x2, l1y1, 10f)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
shapeRenderer.disposeSafely()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Exercises/NetworkedTicTacToe/gradle.properties
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# This doesn't need to be false, and some projects may be able to take advantage of setting daemon to true.
|
||||||
|
# We set it to false by default in order to avoid too many daemons from being created and persisting; each needs RAM.
|
||||||
|
org.gradle.daemon=false
|
||||||
|
# Sets starting memory usage to 512MB, maximum memory usage to 1GB, and tries to set as much to use Unicode as we can.
|
||||||
|
org.gradle.jvmargs=-Xms512M -Xmx1G -Dfile.encoding=UTF-8 -Dconsole.encoding=UTF-8
|
||||||
|
# "Configure on-demand" must be false because it breaks projects that have Android modules. The default is also false.
|
||||||
|
org.gradle.configureondemand=false
|
||||||
|
# The logging level determines which messages get shown about how Gradle itself is working, such as if build.gradle
|
||||||
|
# files are fully future-proof (which they never are, because Gradle constantly deprecates working APIs).
|
||||||
|
# You can change 'quiet' below to 'lifecycle' to use Gradle's default behavior, which shows some confusing messages.
|
||||||
|
# You could instead change 'quiet' below to 'info' to see info that's important mainly while debugging build files.
|
||||||
|
# Note that if you want to use Gradle Build Scans, you should set the below logging level to 'lifecycle', otherwise
|
||||||
|
# the link to the scan won't get shown at all.
|
||||||
|
# Documented at: https://docs.gradle.org/current/userguide/command_line_interface.html#sec:command_line_logging
|
||||||
|
org.gradle.logging.level=quiet
|
||||||
|
ktxVersion=1.13.1-rc1
|
||||||
|
kotlinVersion=2.2.21
|
||||||
|
kotlinxCoroutinesVersion=1.8.1
|
||||||
|
aiVersion=1.8.2
|
||||||
|
artemisOdbVersion=2.3.0
|
||||||
|
ashleyVersion=1.7.4
|
||||||
|
visUiVersion=1.5.7
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableR8.fullMode=false
|
||||||
|
enableGraalNative=false
|
||||||
|
graalHelperVersion=2.0.1
|
||||||
|
gdxVersion=1.14.0
|
||||||
|
projectVersion=1.0.0
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#This file is generated by updateDaemonJvm
|
||||||
|
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/54001d0b636ad500b432d20ef3d580d0/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/54001d0b636ad500b432d20ef3d580d0/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/3ac7a5361c25c0b23d933f44bdb0abd9/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/54001d0b636ad500b432d20ef3d580d0/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/52fa104f4f641439587f75dd68b31bc2/redirect
|
||||||
|
toolchainVersion=17
|
||||||
BIN
Exercises/NetworkedTicTacToe/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
7
Exercises/NetworkedTicTacToe/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0-milestone-1-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
251
Exercises/NetworkedTicTacToe/gradlew
vendored
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (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
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
94
Exercises/NetworkedTicTacToe/gradlew.bat
vendored
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
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 |
8
Exercises/NetworkedTicTacToe/settings.gradle
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
plugins {
|
||||||
|
// Applies the foojay-resolver plugin to allow automatic download of JDKs.
|
||||||
|
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
// if you want to temporarily disable a subproject.
|
||||||
|
include 'android', 'core', 'lwjgl3'
|
||||||