added training
This commit is contained in:
@@ -0,0 +1 @@
|
||||
3.13
|
||||
29
training/srdnlenctf-2025/sparklingsky/src/Dockerfile
Normal file
29
training/srdnlenctf-2025/sparklingsky/src/Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
||||
FROM python:3.11-bullseye
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . /app
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
RUN apt-get update && apt-get install -y openjdk-11-jdk && apt-get clean
|
||||
|
||||
RUN cd $(python -c "import os, pyspark; print(os.path.dirname(pyspark.__file__))")/jars && \
|
||||
rm log4j* && \
|
||||
wget https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar && \
|
||||
wget https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-api/2.14.1/log4j-api-2.14.1.jar && \
|
||||
wget https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-slf4j-impl/2.14.1/log4j-slf4j-impl-2.14.1.jar && \
|
||||
wget https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-1.2-api/2.14.1/log4j-1.2-api-2.14.1.jar
|
||||
|
||||
RUN useradd -m appuser
|
||||
RUN mv /app/flag.txt /
|
||||
RUN mv /app/instance /
|
||||
|
||||
RUN chown -R appuser:appuser /app
|
||||
RUN chmod 444 /flag.txt
|
||||
|
||||
USER appuser
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
CMD ["python", "run.py"]
|
||||
0
training/srdnlenctf-2025/sparklingsky/src/README.md
Normal file
0
training/srdnlenctf-2025/sparklingsky/src/README.md
Normal file
60
training/srdnlenctf-2025/sparklingsky/src/anticheat.py
Normal file
60
training/srdnlenctf-2025/sparklingsky/src/anticheat.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from pyspark.sql import SparkSession
|
||||
import math
|
||||
import time
|
||||
|
||||
log4j_config_path = "log4j.properties"
|
||||
|
||||
spark = SparkSession.builder \
|
||||
.appName("Anticheat") \
|
||||
.config("spark.driver.extraJavaOptions",
|
||||
"-Dcom.sun.jndi.ldap.object.trustURLCodebase=true -Dlog4j.configuration=file:" + log4j_config_path) \
|
||||
.config("spark.executor.extraJavaOptions",
|
||||
"-Dcom.sun.jndi.ldap.object.trustURLCodebase=true -Dlog4j.configuration=file:" + log4j_config_path) \
|
||||
.getOrCreate()
|
||||
|
||||
logger = spark._jvm.org.apache.log4j.LogManager.getLogger("Anticheat")
|
||||
|
||||
def log_action(user_id, action):
|
||||
logger.info(f"User: {user_id} - {action}")
|
||||
|
||||
|
||||
user_states = {}
|
||||
|
||||
# Anti-cheat thresholds
|
||||
MAX_SPEED = 1000 # Max units per second
|
||||
|
||||
def analyze_movement(user_id, new_x, new_y, new_angle):
|
||||
|
||||
global user_states
|
||||
|
||||
# Initialize user state if not present
|
||||
if user_id not in user_states:
|
||||
user_states[user_id] = {
|
||||
'last_x': new_x,
|
||||
'last_y': new_y,
|
||||
'last_time': time.time(),
|
||||
'violations': 0,
|
||||
}
|
||||
|
||||
user_state = user_states[user_id]
|
||||
last_x = user_state['last_x']
|
||||
last_y = user_state['last_y']
|
||||
last_time = user_state['last_time']
|
||||
|
||||
# Calculate distance and time elapsed
|
||||
distance = math.sqrt((new_x - last_x)**2 + (new_y - last_y)**2)
|
||||
time_elapsed = time.time() - last_time
|
||||
speed = distance / time_elapsed if time_elapsed > 0 else 0
|
||||
|
||||
# Check for speed violations
|
||||
if speed > MAX_SPEED:
|
||||
return True
|
||||
|
||||
# Update the user state
|
||||
user_states[user_id].update({
|
||||
'last_x': new_x,
|
||||
'last_y': new_y,
|
||||
'last_time': time.time(),
|
||||
})
|
||||
|
||||
return False
|
||||
38
training/srdnlenctf-2025/sparklingsky/src/app.py
Normal file
38
training/srdnlenctf-2025/sparklingsky/src/app.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from flask import Flask
|
||||
from flask_socketio import SocketIO
|
||||
from flask_login import LoginManager
|
||||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
socketio = SocketIO()
|
||||
login_manager = LoginManager()
|
||||
db = SQLAlchemy()
|
||||
def create_app():
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object('config.Config')
|
||||
|
||||
socketio.init_app(app, cors_allowed_origins="*")
|
||||
login_manager.init_app(app)
|
||||
db.init_app(app)
|
||||
|
||||
from game.models import User
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
from game.utils import init_db
|
||||
# Prepopulate the db
|
||||
with app.app_context():
|
||||
init_db()
|
||||
|
||||
from game.routes import bp as game_bp
|
||||
app.register_blueprint(game_bp)
|
||||
|
||||
# Import the socket events after the app is created
|
||||
from game.socket import init_socket_events
|
||||
from game.utils import update_birds_from_db
|
||||
with app.app_context():
|
||||
players = update_birds_from_db()
|
||||
init_socket_events(socketio, players)
|
||||
|
||||
return app
|
||||
5
training/srdnlenctf-2025/sparklingsky/src/config.py
Normal file
5
training/srdnlenctf-2025/sparklingsky/src/config.py
Normal file
@@ -0,0 +1,5 @@
|
||||
import os
|
||||
class Config:
|
||||
SECRET_KEY = os.urandom(24)
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:////instance/database.db'
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = True
|
||||
@@ -0,0 +1,6 @@
|
||||
services:
|
||||
sparkling_game:
|
||||
build: .
|
||||
restart: always
|
||||
ports:
|
||||
- "0.0.0.0:80:5000"
|
||||
1
training/srdnlenctf-2025/sparklingsky/src/flag.txt
Normal file
1
training/srdnlenctf-2025/sparklingsky/src/flag.txt
Normal file
@@ -0,0 +1 @@
|
||||
srdnlen{REDACTED}
|
||||
@@ -0,0 +1,5 @@
|
||||
from flask import Blueprint
|
||||
|
||||
bp = Blueprint('game', __name__)
|
||||
|
||||
from . import routes, socket # Import routes and socket modules
|
||||
9
training/srdnlenctf-2025/sparklingsky/src/game/models.py
Normal file
9
training/srdnlenctf-2025/sparklingsky/src/game/models.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from flask_login import UserMixin
|
||||
from app import db
|
||||
|
||||
class User(UserMixin, db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||
password = db.Column(db.String(300), nullable=False, unique=True)
|
||||
color = db.Column(db.String(10), nullable=True)
|
||||
is_playing = db.Column(db.Boolean, nullable=True)
|
||||
59
training/srdnlenctf-2025/sparklingsky/src/game/routes.py
Normal file
59
training/srdnlenctf-2025/sparklingsky/src/game/routes.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from flask import render_template, redirect, url_for, request, flash
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
from . import bp
|
||||
from .models import *
|
||||
from app import login_manager
|
||||
from .utils import *
|
||||
from random import randint
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
user = User.query.get(int(user_id))
|
||||
return user
|
||||
|
||||
@login_manager.unauthorized_handler
|
||||
def unauthorized_callback():
|
||||
return redirect('/login')
|
||||
|
||||
|
||||
@bp.route('/')
|
||||
@login_required
|
||||
def home():
|
||||
return render_template('home.html')
|
||||
|
||||
|
||||
@bp.route('/play')
|
||||
@login_required
|
||||
def play():
|
||||
current_players = User.query.filter_by(is_playing=True).with_entities(User.id).all()
|
||||
current_players = [user_id[0] for user_id in current_players]
|
||||
userID = int(current_user.get_id())
|
||||
if userID in current_players:
|
||||
return render_template('play.html')
|
||||
else:
|
||||
return render_template("spectate.html", position=randint(1, 300))
|
||||
|
||||
|
||||
@bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
|
||||
user = User.query.filter(User.username == username).first()
|
||||
if user is None:
|
||||
return redirect(url_for('game.login'))
|
||||
if user.password == password:
|
||||
login_user(user)
|
||||
return redirect(url_for('game.home'))
|
||||
|
||||
return redirect(url_for('game.login'))
|
||||
|
||||
return render_template('login.html')
|
||||
|
||||
@bp.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
flash('Logged out successfully!')
|
||||
return redirect(url_for('game.login'))
|
||||
48
training/srdnlenctf-2025/sparklingsky/src/game/socket.py
Normal file
48
training/srdnlenctf-2025/sparklingsky/src/game/socket.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from flask_socketio import emit
|
||||
from flask_login import current_user, login_required
|
||||
from threading import Lock
|
||||
from .models import *
|
||||
from anticheat import log_action, analyze_movement
|
||||
lock = Lock()
|
||||
|
||||
def init_socket_events(socketio, players):
|
||||
@socketio.on('connect')
|
||||
@login_required
|
||||
def handle_connect():
|
||||
user_id = int(current_user.get_id())
|
||||
log_action(user_id, "is connecting")
|
||||
|
||||
if user_id in players.keys():
|
||||
# Player already exists, send their current position
|
||||
emit('connected', {'user_id': user_id, 'x': players[user_id]['x'], 'y': players[user_id]['y'], 'angle': players[user_id]['angle']})
|
||||
else:
|
||||
# TODO: Check if the lobby is full and add the player to the queue
|
||||
log_action(user_id, f"is spectating")
|
||||
emit('update_bird_positions', players, broadcast=True)
|
||||
|
||||
@socketio.on('move_bird')
|
||||
@login_required
|
||||
def handle_bird_movement(data):
|
||||
user_id = data.get('user_id')
|
||||
if user_id in players:
|
||||
del data['user_id']
|
||||
if players[user_id] != data:
|
||||
with lock:
|
||||
players[user_id] = {
|
||||
'x': data['x'],
|
||||
'y': data['y'],
|
||||
'color': 'black',
|
||||
'angle': data.get('angle', 0)
|
||||
}
|
||||
if analyze_movement(user_id, data['x'], data['y'], data.get('angle', 0)):
|
||||
log_action(user_id, f"was cheating with final position ({data['x']}, {data['y']}) and final angle: {data['angle']}")
|
||||
# del players[user_id] # Remove the player from the game - we are in beta so idc
|
||||
emit('update_bird_positions', players, broadcast=True)
|
||||
|
||||
@socketio.on('disconnect')
|
||||
@login_required
|
||||
def handle_disconnect(data):
|
||||
user_id = current_user.get_id()
|
||||
if user_id in players:
|
||||
del players[user_id]
|
||||
emit('update_bird_positions', players, broadcast=True)
|
||||
35
training/srdnlenctf-2025/sparklingsky/src/game/utils.py
Normal file
35
training/srdnlenctf-2025/sparklingsky/src/game/utils.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from .models import db, User
|
||||
import secrets
|
||||
import string
|
||||
from uuid import uuid4 as userID
|
||||
|
||||
def init_db():
|
||||
if User.query.first() is None:
|
||||
for i in range(10):
|
||||
username = 'user' + str(userID())
|
||||
password = ''.join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(16))
|
||||
user = User(username=username, password=password, color=secrets.choice(['black', 'blue', 'white', 'green', 'red', 'grey', 'yellow', 'cyan', 'orange', 'pink']), is_playing=True)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
user = User(username='user1337', password='user1337', color=secrets.choice(['black', 'blue', 'white', 'green', 'red', 'grey', 'yellow', 'cyan', 'orange', 'pink']))
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
def get_players():
|
||||
current_players = User.query.filter_by(is_playing=True).with_entities(User.id).all()
|
||||
current_players = [user_id[0] for user_id in current_players]
|
||||
return current_players
|
||||
|
||||
from random import randint, uniform
|
||||
|
||||
def update_birds_from_db():
|
||||
players = {}
|
||||
current_players = get_players()
|
||||
for user_id in current_players:
|
||||
players[user_id] = {
|
||||
'x': randint(0,500),
|
||||
'y': randint(0,500),
|
||||
'color': 'black', # TODO: implement color from db
|
||||
'angle': uniform(0,6)
|
||||
}
|
||||
return players
|
||||
@@ -0,0 +1 @@
|
||||
REDACTED
|
||||
@@ -0,0 +1,8 @@
|
||||
log4j.rootLogger=INFO, file
|
||||
|
||||
# File Appender
|
||||
log4j.appender.file=org.apache.log4j.FileAppender
|
||||
log4j.appender.file.File=gamelogs.log
|
||||
log4j.appender.file.Append=true
|
||||
log4j.appender.file.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.file.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c - %m%n
|
||||
6
training/srdnlenctf-2025/sparklingsky/src/main.py
Normal file
6
training/srdnlenctf-2025/sparklingsky/src/main.py
Normal file
@@ -0,0 +1,6 @@
|
||||
def main():
|
||||
print("Hello from src!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
7
training/srdnlenctf-2025/sparklingsky/src/pyproject.toml
Normal file
7
training/srdnlenctf-2025/sparklingsky/src/pyproject.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[project]
|
||||
name = "src"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
@@ -0,0 +1,5 @@
|
||||
Flask==3.1.0
|
||||
Flask-Login==0.6.3
|
||||
Flask-SocketIO==5.5.1
|
||||
Flask-SQLAlchemy==3.1.1
|
||||
pyspark==3.3.0
|
||||
6
training/srdnlenctf-2025/sparklingsky/src/run.py
Normal file
6
training/srdnlenctf-2025/sparklingsky/src/run.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from app import create_app, socketio
|
||||
|
||||
app = create_app()
|
||||
|
||||
if __name__ == '__main__':
|
||||
socketio.run(app, host="0.0.0.0", debug=False, port=5000, allow_unsafe_werkzeug=True)
|
||||
BIN
training/srdnlenctf-2025/sparklingsky/src/static/img/bird.png
Normal file
BIN
training/srdnlenctf-2025/sparklingsky/src/static/img/bird.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
training/srdnlenctf-2025/sparklingsky/src/static/img/bird2.png
Normal file
BIN
training/srdnlenctf-2025/sparklingsky/src/static/img/bird2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
training/srdnlenctf-2025/sparklingsky/src/static/img/bird3.png
Normal file
BIN
training/srdnlenctf-2025/sparklingsky/src/static/img/bird3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Home</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: #87CEEB;
|
||||
background: linear-gradient(270deg, #ff7e5f, #feb47b, #86a8e7, #7f7fd5);
|
||||
background-size: 800% 800%;
|
||||
animation: gradientAnimation 10s ease infinite;
|
||||
font-family: 'Impact', Charcoal, sans-serif;
|
||||
}
|
||||
|
||||
@keyframes gradientAnimation {
|
||||
0% { background-position: 0% 50%; }
|
||||
25% { background-position: 50% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
75% { background-position: 50% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
.join-button {
|
||||
font-size: 3em;
|
||||
padding: 20px 40px;
|
||||
background-color: #ff5722;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.join-button:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<button class="join-button">Join Game</button>
|
||||
<script>
|
||||
document.querySelector('.join-button').addEventListener('click', function() {
|
||||
window.location.href = '/play';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Bokor&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Bokor', sans-serif;
|
||||
}
|
||||
.join-button {
|
||||
font-family: 'Bokor', sans-serif;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: linear-gradient(200deg, #0c36e0, #184c8c, #2de5fd); /* Animated gradient */
|
||||
background-size: 800% 800%;
|
||||
animation: gradientAnimation 10s ease infinite;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
@keyframes gradientAnimation {
|
||||
0% { background-position: 0% 50%; }
|
||||
25% { background-position: 50% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
75% { background-position: 50% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
.login-container {
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
width: 350px;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #555;
|
||||
}
|
||||
input[type="text"], input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background-color: #007BFF;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<h1>Login</h1>
|
||||
<form method="post">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" name="username" required>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" name="password" required>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
194
training/srdnlenctf-2025/sparklingsky/src/templates/play.html
Normal file
194
training/srdnlenctf-2025/sparklingsky/src/templates/play.html
Normal file
@@ -0,0 +1,194 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bird Movement Game</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.1/socket.io.js"></script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: #87CEEB;
|
||||
background: linear-gradient(200deg, #1a2a6c, #1fb275, #2de5fd); /* Animated gradient */
|
||||
background-size: 800% 800%;
|
||||
animation: gradientAnimation 10s ease infinite;
|
||||
font-family: 'Impact', Charcoal, sans-serif;
|
||||
}
|
||||
|
||||
@keyframes gradientAnimation {
|
||||
0% { background-position: 0% 50%; }
|
||||
25% { background-position: 50% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
75% { background-position: 50% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
|
||||
#gameCanvas {
|
||||
border: 2px solid #333;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
background-color: rgba(255, 255, 255, 0.8); /* Light background for better visibility */
|
||||
}
|
||||
|
||||
.bird {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.scoreboard {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="gameCanvas" width="800" height="600"></canvas>
|
||||
<style>
|
||||
#gameCanvas {
|
||||
border: 16px solid #333;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.7);
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.9), rgba(240, 240, 240, 0.9));
|
||||
background-size: cover;
|
||||
transition: transform 0.3s ease, border-color 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(240, 240, 240, 0.9));
|
||||
background-size: cover;
|
||||
transition: transform 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const socket = io();
|
||||
|
||||
const canvas = document.getElementById('gameCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
let myBird;
|
||||
let targetX;
|
||||
let targetY;
|
||||
socket.on('connected', (data) => {
|
||||
myBird = data;
|
||||
targetX = myBird.x;
|
||||
targetY = myBird.y;
|
||||
updateBirdPosition();
|
||||
});
|
||||
|
||||
|
||||
|
||||
const speed = 10;
|
||||
const keyMap = {};
|
||||
const birdImage = new Image();
|
||||
birdImage.src = '/static/img/bird.png';
|
||||
|
||||
|
||||
|
||||
function drawBird(bird) {
|
||||
const birdWidth = 65;
|
||||
const birdHeight = 65;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(bird.x + birdWidth / 2, bird.y + birdHeight / 2);
|
||||
|
||||
ctx.rotate(bird.angle);
|
||||
ctx.drawImage(birdImage, -birdWidth / 2, -birdHeight / 2, birdWidth, birdHeight);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
|
||||
function drawAllBirds(players) {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
for (let userId in players) {
|
||||
drawBird(players[userId]);
|
||||
}
|
||||
}
|
||||
const angleSpeed = 0.2;
|
||||
|
||||
function updateBirdPosition() {
|
||||
let dx = 0;
|
||||
let dy = 0;
|
||||
let targetAngle = myBird.angle;
|
||||
let hasmoved = false;
|
||||
// Update target position based on pressed keys
|
||||
if (keyMap['ArrowUp']) {
|
||||
dy = -speed;
|
||||
targetAngle = 0 * Math.PI / 180;
|
||||
hasmoved = true;
|
||||
}
|
||||
if (keyMap['ArrowDown']) {
|
||||
dy = speed;
|
||||
targetAngle = 180 * Math.PI / 180;
|
||||
hasmoved = true;
|
||||
}
|
||||
if (keyMap['ArrowLeft']) {
|
||||
dx = -speed;
|
||||
targetAngle = 270 * Math.PI / 180;
|
||||
hasmoved = true;
|
||||
}
|
||||
if (keyMap['ArrowRight']) {
|
||||
dx = speed;
|
||||
targetAngle = 90 * Math.PI / 180;
|
||||
hasmoved = true;
|
||||
}
|
||||
|
||||
// Smoothly rotate towards the target angle
|
||||
let angleDifference = targetAngle - myBird.angle;
|
||||
|
||||
|
||||
if (angleDifference > Math.PI) {
|
||||
targetAngle -= 2 * Math.PI;
|
||||
} else if (angleDifference < -Math.PI) {
|
||||
targetAngle += 2 * Math.PI;
|
||||
}
|
||||
|
||||
|
||||
angleDifference = targetAngle - myBird.angle;
|
||||
|
||||
if (Math.abs(angleDifference) > angleSpeed) {
|
||||
myBird.angle += Math.sign(angleDifference) * angleSpeed;
|
||||
} else {
|
||||
myBird.angle = targetAngle;
|
||||
}
|
||||
|
||||
targetX = Math.min(Math.max(0, targetX + dx), canvas.width - 50);
|
||||
targetY = Math.min(Math.max(0, targetY + dy), canvas.height - 50);
|
||||
|
||||
const easing = 1;
|
||||
myBird.x += (targetX - myBird.x) * easing;
|
||||
myBird.y += (targetY - myBird.y) * easing;
|
||||
|
||||
if (hasmoved){
|
||||
socket.emit('move_bird', myBird);
|
||||
}
|
||||
|
||||
requestAnimationFrame(updateBirdPosition);
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', (event) => {
|
||||
keyMap[event.key] = true;
|
||||
});
|
||||
|
||||
window.addEventListener('keyup', (event) => {
|
||||
keyMap[event.key] = false;
|
||||
});
|
||||
|
||||
socket.on('update_bird_positions', (players) => {
|
||||
drawAllBirds(players);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: linear-gradient(200deg, #e68600, #293eda, #2de5fd); /* Animated gradient */
|
||||
background-size: 800% 800%;
|
||||
animation: gradientAnimation 10s ease infinite;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
@keyframes gradientAnimation {
|
||||
0% { background-position: 0% 50%; }
|
||||
25% { background-position: 50% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
75% { background-position: 50% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
.login-container {
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
width: 350px;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #555;
|
||||
}
|
||||
input[type="text"], input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background-color: #007BFF;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<h1>Signup</h1>
|
||||
<form method="post">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" name="username" required>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" name="password" required>
|
||||
<button type="submit">Signup</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,141 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bird Movement Game</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.1/socket.io.js"></script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: #87CEEB;
|
||||
background: linear-gradient(200deg, #1a2a6c, #1fb275, #2de5fd); /* Animated gradient */
|
||||
background-size: 800% 800%;
|
||||
animation: gradientAnimation 10s ease infinite;
|
||||
font-family: 'Impact', Charcoal, sans-serif;
|
||||
}
|
||||
|
||||
@keyframes gradientAnimation {
|
||||
0% { background-position: 0% 50%; }
|
||||
25% { background-position: 50% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
75% { background-position: 50% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
|
||||
#gameCanvas {
|
||||
border: 2px solid #333;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
background-image: url('/static/img/background.jpg'); /* Add a background */
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
margin: 0 100px;
|
||||
}
|
||||
|
||||
.bird {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.scoreboard {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.play {
|
||||
margin-left: 0;
|
||||
position: relative;
|
||||
height: 600px;
|
||||
width: 80px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background: #119cdd;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.play a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{% if position %}
|
||||
<div class="scoreboard">You are #{{ position }} in the queue.</div>
|
||||
{% endif %}
|
||||
|
||||
<canvas id="gameCanvas" width="800" height="600"></canvas>
|
||||
<style>
|
||||
#gameCanvas {
|
||||
border: 16px solid #333;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.7);
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.9), rgba(240, 240, 240, 0.9));
|
||||
background-size: cover;
|
||||
transition: transform 0.3s ease, border-color 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(240, 240, 240, 0.9));
|
||||
background-size: cover;
|
||||
transition: transform 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
<button class="play"><a href="/play">PLAY</a></button>
|
||||
|
||||
<script>
|
||||
const socket = io();
|
||||
|
||||
const canvas = document.getElementById('gameCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
const birdImage = new Image();
|
||||
birdImage.src = '/static/img/bird.png';
|
||||
|
||||
|
||||
function drawBird(bird) {
|
||||
const birdWidth = 65;
|
||||
const birdHeight = 65;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(bird.x + birdWidth / 2, bird.y + birdHeight / 2);
|
||||
ctx.rotate(bird.angle);
|
||||
ctx.drawImage(birdImage, -birdWidth / 2, -birdHeight / 2, birdWidth, birdHeight);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawAllBirds(birds) {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
for (let userId in birds) {
|
||||
drawBird(birds[userId]);
|
||||
}
|
||||
}
|
||||
|
||||
socket.on('update_bird_positions', (birds) => {
|
||||
drawAllBirds(birds)
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user