fix: correct shebang and executable permissions

- Add +x to Python scripts with shebangs (3 files)
- Remove -x from non-script files like .cpp, .txt, makefile (23 files)
- Move shebang to first line in C/imageViewer/lint.sh
This commit is contained in:
Krzysztof kuhy Rudnicki 2025-11-30 13:42:16 +01:00
parent 5a6095bd8f
commit e3f9e6dc0b
120 changed files with 5408 additions and 1143 deletions

View File

@ -2,19 +2,19 @@ name: Python tests
on: on:
push: push:
branches: [ main ] branches: [main]
paths: paths:
- 'PYTHON/lichess_bot/**' - "PYTHON/lichess_bot/**"
- 'PYTHON/**' - "PYTHON/**"
- 'tests/**' - "tests/**"
- 'requirements.txt' - "requirements.txt"
pull_request: pull_request:
branches: [ main ] branches: [main]
paths: paths:
- 'PYTHON/lichess_bot/**' - "PYTHON/lichess_bot/**"
- 'PYTHON/**' - "PYTHON/**"
- 'tests/**' - "tests/**"
- 'requirements.txt' - "requirements.txt"
jobs: jobs:
test: test:
@ -23,7 +23,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: '3.11' python-version: "3.11"
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip

6
.gitignore vendored
View File

@ -227,9 +227,9 @@ cython_debug/
.abstra/ .abstra/
# Visual Studio Code # Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer, # and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder # you could uncomment the following to ignore the entire vscode folder
# .vscode/ # .vscode/
@ -247,4 +247,4 @@ __marimo__/
# Streamlit # Streamlit
.streamlit/secrets.toml .streamlit/secrets.toml
fps_demo fps_demo
server_c server_c

268
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,268 @@
# ==============================================================================
# Pre-commit Configuration - AGGRESSIVE Python Linting & Formatting
# ==============================================================================
# Install: pre-commit install
# Run all: pre-commit run --all-files
# Update hooks: pre-commit autoupdate
# ==============================================================================
# Global settings
default_language_version:
python: python3
# Fail fast on first error (set to false to see all errors)
fail_fast: false
# Configuration
ci:
autofix_commit_msg: "style: auto-fix by pre-commit hooks"
autoupdate_commit_msg: "chore: update pre-commit hooks"
repos:
# ===========================================================================
# GENERAL HOOKS - File formatting and validation
# ===========================================================================
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer
- id: check-yaml
args: [--unsafe]
- id: check-json
- id: check-toml
- id: check-xml
- id: check-added-large-files
args: [--maxkb=1000]
- id: check-merge-conflict
- id: check-case-conflict
- id: check-symlinks
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- id: detect-private-key
- id: debug-statements
- id: name-tests-test
args: [--pytest-test-first]
- id: check-ast
- id: check-builtin-literals
- id: check-docstring-first
- id: fix-byte-order-marker
- id: mixed-line-ending
args: [--fix=lf]
- id: requirements-txt-fixer
# ===========================================================================
# RUFF - Fast Python linter and formatter (replaces black, isort, flake8, etc.)
# ===========================================================================
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.1
hooks:
# Linter - run first to catch issues
- id: ruff
args:
- --fix
- --exit-non-zero-on-fix
- --show-fixes
types_or: [python, pyi]
# Formatter - run after linting
- id: ruff-format
types_or: [python, pyi]
# ===========================================================================
# MYPY - Static type checking (strict mode)
# ===========================================================================
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
hooks:
- id: mypy
args:
- --strict
- --ignore-missing-imports
- --show-error-codes
- --no-error-summary
additional_dependencies:
- types-requests
- types-PyYAML
- types-python-dateutil
exclude: ^(Bash/|\.venv/)
# ===========================================================================
# PYLINT - Comprehensive Python linter
# ===========================================================================
- repo: https://github.com/pylint-dev/pylint
rev: v3.3.2
hooks:
- id: pylint
args:
- --rcfile=pyproject.toml
- --fail-under=5.0
- --jobs=0
additional_dependencies:
- python-chess
- requests
- pygame
exclude: ^(Bash/|\.venv/)
# ===========================================================================
# BANDIT - Security linter
# ===========================================================================
- repo: https://github.com/PyCQA/bandit
rev: 1.7.10
hooks:
- id: bandit
args:
- -c
- pyproject.toml
- --severity-level=low
- --confidence-level=low
additional_dependencies: ["bandit[toml]"]
exclude: ^(Bash/|\.venv/|tests/|.*test.*\.py$)
# ===========================================================================
# VULTURE - Dead code detection
# ===========================================================================
- repo: https://github.com/jendrikseipp/vulture
rev: v2.13
hooks:
- id: vulture
args:
- --min-confidence=80
- --exclude=.venv,Bash,__pycache__
exclude: ^(Bash/|\.venv/)
# ===========================================================================
# PYUPGRADE - Upgrade Python syntax
# ===========================================================================
- repo: https://github.com/asottile/pyupgrade
rev: v3.19.0
hooks:
- id: pyupgrade
args:
- --py310-plus
# ===========================================================================
# CODESPELL - Spell checking in code
# ===========================================================================
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
hooks:
- id: codespell
args:
- --skip=*.json,*.lock,*.min.js,*.min.css,.git,__pycache__,.venv
- --ignore-words-list=ans,ect,nd,som,sur
exclude: ^(Bash/ffmpeg-build/)
# ===========================================================================
# DOCFORMATTER - Format docstrings (using local hook due to compatibility)
# ===========================================================================
- repo: local
hooks:
- id: docformatter
name: docformatter
entry: docformatter
language: system
types: [python]
args:
- --in-place
- --wrap-summaries=88
- --wrap-descriptions=88
# ===========================================================================
# INTERROGATE - Docstring coverage
# ===========================================================================
- repo: https://github.com/econchick/interrogate
rev: 1.7.0
hooks:
- id: interrogate
args:
- --fail-under=0
- --verbose
- --ignore-init-method
- --ignore-init-module
- --ignore-magic
- --ignore-private
- --ignore-semiprivate
- --exclude=Bash,.venv,__pycache__
pass_filenames: false
# ===========================================================================
# AUTOFLAKE - Remove unused imports/variables
# ===========================================================================
- repo: https://github.com/PyCQA/autoflake
rev: v2.3.1
hooks:
- id: autoflake
args:
- --in-place
- --remove-all-unused-imports
- --remove-unused-variables
- --remove-duplicate-keys
- --expand-star-imports
# ===========================================================================
# SAFETY - Check for security vulnerabilities in dependencies
# ===========================================================================
# Note: Safety requires API key for full functionality, disabled by default
# - repo: https://github.com/Lucas-C/pre-commit-hooks-safety
# rev: v1.3.2
# hooks:
# - id: python-safety-dependencies-check
# files: requirements.*\.txt$
# ===========================================================================
# PYRIGHT - Microsoft's type checker (very strict, optional)
# ===========================================================================
# Uncomment to enable - can be slow and very strict
# - repo: https://github.com/RobertCraiworthy/pyright-action
# rev: v1.1.350
# hooks:
# - id: pyright
# ===========================================================================
# FLAKE8 - Traditional linter (supplementary to ruff)
# ===========================================================================
- repo: https://github.com/PyCQA/flake8
rev: 7.1.1
hooks:
- id: flake8
args:
- --max-line-length=88
- --extend-ignore=E203,W503
- --max-complexity=10
- --statistics
additional_dependencies:
- flake8-bugbear
- flake8-comprehensions
- flake8-simplify
- flake8-print
exclude: ^(Bash/|\.venv/)
# ===========================================================================
# CHECK JSON/YAML/TOML formatting
# ===========================================================================
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
hooks:
- id: prettier
types_or: [yaml, json, markdown]
exclude: ^(Bash/|\.venv/|.*\.lock$)
# ===========================================================================
# SHELLCHECK - Shell script linting
# ===========================================================================
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.10.0.1
hooks:
- id: shellcheck
args: [--severity=warning]
# ===========================================================================
# COMMITIZEN - Conventional commits (optional)
# ===========================================================================
# - repo: https://github.com/commitizen-tools/commitizen
# rev: v3.13.0
# hooks:
# - id: commitizen
# - id: commitizen-branch
# stages: [push]

10
.vscode/settings.json vendored
View File

@ -1,6 +1,6 @@
{ {
"files.associations": { "files.associations": {
"*.py": "python", "*.py": "python",
"stdio.h": "c" "stdio.h": "c"
} }
} }

124
.vscode/tasks.json vendored
View File

@ -1,64 +1,62 @@
{ {
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{ {
"label": "pytest quick", "label": "pytest quick",
"type": "shell", "type": "shell",
"command": "python -m pip install -r requirements.txt && pytest -q", "command": "python -m pip install -r requirements.txt && pytest -q",
"problemMatcher": [ "problemMatcher": ["$pytest"],
"$pytest" "group": "build"
], },
"group": "build" {
}, "label": "pytest quick",
{ "type": "shell",
"label": "pytest quick", "command": "python -m pip install -r requirements.txt && pytest -q",
"type": "shell", "isBackground": false,
"command": "python -m pip install -r requirements.txt && pytest -q", "group": "build"
"isBackground": false, },
"group": "build" {
}, "label": "pytest quick",
{ "type": "shell",
"label": "pytest quick", "command": "python -m pip install -r requirements.txt && pytest -q",
"type": "shell", "isBackground": false,
"command": "python -m pip install -r requirements.txt && pytest -q", "group": "build"
"isBackground": false, },
"group": "build" {
}, "label": "pytest quick",
{ "type": "shell",
"label": "pytest quick", "command": "python -m pip install -r requirements.txt && pytest -q",
"type": "shell", "group": "build"
"command": "python -m pip install -r requirements.txt && pytest -q", },
"group": "build" {
}, "label": "pytest quick",
{ "type": "shell",
"label": "pytest quick", "command": "python -m pip install -r requirements.txt && pytest -q",
"type": "shell", "group": "build"
"command": "python -m pip install -r requirements.txt && pytest -q", },
"group": "build" {
}, "label": "pytest quick",
{ "type": "shell",
"label": "pytest quick", "command": "python -m pip install -r requirements.txt && pytest -q",
"type": "shell", "group": "build"
"command": "python -m pip install -r requirements.txt && pytest -q", },
"group": "build" {
}, "label": "pytest quick",
{ "type": "shell",
"label": "pytest quick", "command": "python -m pip install -r requirements.txt && pytest -q",
"type": "shell", "group": "build"
"command": "python -m pip install -r requirements.txt && pytest -q", },
"group": "build" {
}, "label": "pytest quick",
{ "type": "shell",
"label": "pytest quick", "command": "python -m pip install -r requirements.txt && pytest -q",
"type": "shell", "group": "build"
"command": "python -m pip install -r requirements.txt && pytest -q", },
"group": "build" {
}, "label": "pytest quick",
{ "type": "shell",
"label": "pytest quick", "command": "python -m pip install -r requirements.txt && pytest -q",
"type": "shell", "group": "build"
"command": "python -m pip install -r requirements.txt && pytest -q", }
"group": "build" ]
} }
]
}

@ -0,0 +1 @@
Subproject commit 0bc54cddb1050c3c55bc65adbd3c8aa90d7eb457

View File

@ -14,5 +14,5 @@ WarningsAsErrors: >
cert-err33-c, cert-err33-c,
cert-err34-c, cert-err34-c,
cert-fio38-c cert-fio38-c
HeaderFilterRegex: '.*' HeaderFilterRegex: ".*"
FormatStyle: none FormatStyle: none

2
C/.gitignore vendored
View File

@ -1 +1 @@
random_engine random_engine

View File

@ -1,168 +1,168 @@
#include <math.h> #include <math.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <windows.h> #include <windows.h>
#define LINE_LENGTH 100 #define LINE_LENGTH 100
void C() void C()
{ {
printf("\nCheck\n"); printf("\nCheck\n");
return; return;
} }
void printAcceleration(int acceleration) void printAcceleration(int acceleration)
{ {
printf("The value of acceleration is: %d\n", acceleration); printf("The value of acceleration is: %d\n", acceleration);
system("PAUSE"); system("PAUSE");
return; return;
} }
void pauseSystem() { system("PAUSE"); } void pauseSystem() { system("PAUSE"); }
void clearScreen() void clearScreen()
{ {
system("CLS"); system("CLS");
return; return;
} }
void pauseForASecond() void pauseForASecond()
{ {
Sleep(1000); Sleep(1000);
return; return;
} }
void pauseForGivenTime(float given_time) void pauseForGivenTime(float given_time)
{ {
Sleep(fabs(given_time * 1000)); Sleep(fabs(given_time * 1000));
return; return;
} }
float calculateVelocity(float starting_velocity, unsigned int physics_time, int *acceleration) float calculateVelocity(float starting_velocity, unsigned int physics_time, int *acceleration)
{ {
return (*acceleration) * physics_time + starting_velocity; return (*acceleration) * physics_time + starting_velocity;
} }
int calculateDisplacement(float starting_velocity, int *acceleration, unsigned int physics_time) int calculateDisplacement(float starting_velocity, int *acceleration, unsigned int physics_time)
{ {
return starting_velocity * physics_time + ((1 / 2) * (*acceleration) * (physics_time ^ 2)); return starting_velocity * physics_time + ((1 / 2) * (*acceleration) * (physics_time ^ 2));
} }
void printXPosition(int position) void printXPosition(int position)
{ {
printf("\nx position is: %d\n", position); printf("\nx position is: %d\n", position);
return; return;
} }
void printClock(unsigned int *time) void printClock(unsigned int *time)
{ {
printf("%d seconds passed\n", *time); printf("%d seconds passed\n", *time);
return; return;
} }
float calculateStopTime(float velocity) { return 1 / velocity; } float calculateStopTime(float velocity) { return 1 / velocity; }
void printLine(int position) void printLine(int position)
{ {
clearScreen(); clearScreen();
for (int i = -(LINE_LENGTH / 2); i < LINE_LENGTH / 2; i++) for (int i = -(LINE_LENGTH / 2); i < LINE_LENGTH / 2; i++)
{ {
if (i == position) if (i == position)
printf("x"); printf("x");
else else
printf("-"); printf("-");
} }
return; return;
} }
void printVelocity(float velocity) void printVelocity(float velocity)
{ {
printf("Velocity is: %f\n", velocity); printf("Velocity is: %f\n", velocity);
return; return;
} }
int calculateTimePassed(float velocity) int calculateTimePassed(float velocity)
{ {
if (velocity >= 1 || velocity <= -1) if (velocity >= 1 || velocity <= -1)
return 1; return 1;
else else
{ {
printf("Time passed is: %f\n", fabs(1 / velocity)); printf("Time passed is: %f\n", fabs(1 / velocity));
return fabs(1 / velocity); return fabs(1 / velocity);
} }
} }
void printAllInfo(int position, unsigned int *time, float *velocity) void printAllInfo(int position, unsigned int *time, float *velocity)
{ {
pauseForGivenTime(calculateStopTime(*velocity)); pauseForGivenTime(calculateStopTime(*velocity));
printLine(position); printLine(position);
printXPosition(position); printXPosition(position);
*time += calculateTimePassed(*velocity); *time += calculateTimePassed(*velocity);
printClock(time); printClock(time);
printVelocity(*velocity); printVelocity(*velocity);
// pauseForASecond(); // pauseForASecond();
return; return;
} }
float chooseVelocity() float chooseVelocity()
{ {
float velocity; float velocity;
printf("Write velocity of the object in m / s: "); printf("Write velocity of the object in m / s: ");
scanf("%f", &velocity); scanf("%f", &velocity);
return velocity; return velocity;
} }
int chooseAcceleration() int chooseAcceleration()
{ {
int acceleration; int acceleration;
printf("Choose acceleration of the object in m / (s ^ 2):"); printf("Choose acceleration of the object in m / (s ^ 2):");
scanf("%d", &acceleration); scanf("%d", &acceleration);
return acceleration; return acceleration;
} }
int outOfLine(int position) int outOfLine(int position)
{ {
if ((position < LINE_LENGTH / 2) && (position > -1 * (LINE_LENGTH / 2))) if ((position < LINE_LENGTH / 2) && (position > -1 * (LINE_LENGTH / 2)))
{ {
return 0; return 0;
} }
else else
return 1; return 1;
} }
void moveUntillOutOfLine(int position, unsigned int *time) void moveUntillOutOfLine(int position, unsigned int *time)
{ {
while (!outOfLine(position)) while (!outOfLine(position))
{ {
float velocity = chooseVelocity(); float velocity = chooseVelocity();
float *Pvelocity = &velocity; float *Pvelocity = &velocity;
position += calculateDisplacement(velocity, 0, 1); position += calculateDisplacement(velocity, 0, 1);
printAllInfo(position, time, Pvelocity); printAllInfo(position, time, Pvelocity);
} }
return; return;
} }
void moveUntillOutOfVelocity(int position, int *acceleration, unsigned int *time) void moveUntillOutOfVelocity(int position, int *acceleration, unsigned int *time)
{ {
float velocity = 0; float velocity = 0;
float *Pvelocity = &velocity; float *Pvelocity = &velocity;
while (!outOfLine(position)) while (!outOfLine(position))
{ {
position += calculateDisplacement(velocity, acceleration, 1); position += calculateDisplacement(velocity, acceleration, 1);
printXPosition(position); printXPosition(position);
pauseSystem(); pauseSystem();
velocity = calculateVelocity(velocity, 1, acceleration); velocity = calculateVelocity(velocity, 1, acceleration);
printAllInfo(position, time, Pvelocity); printAllInfo(position, time, Pvelocity);
} }
return; return;
} }
int main() int main()
{ {
int position = 0, acceleration = -1; int position = 0, acceleration = -1;
int *Pacceleration = &acceleration; int *Pacceleration = &acceleration;
unsigned int time = 0; unsigned int time = 0;
unsigned int *Ptime = &time; unsigned int *Ptime = &time;
moveUntillOutOfLine(position, Ptime); moveUntillOutOfLine(position, Ptime);
// moveUntillOutOfVelocity(position, Pacceleration, Ptime); // moveUntillOutOfVelocity(position, Pacceleration, Ptime);
return 0; return 0;
} }

View File

@ -1,6 +1,7 @@
# Simple OpenGL FPS (C + FreeGLUT) # Simple OpenGL FPS (C + FreeGLUT)
A tiny first-person demo using legacy OpenGL (compat) and FreeGLUT: A tiny first-person demo using legacy OpenGL (compat) and FreeGLUT:
- Move with WASD, hold Tab or Q to sprint - Move with WASD, hold Tab or Q to sprint
- Aim with mouse (captured by default). Press M to toggle capture - Aim with mouse (captured by default). Press M to toggle capture
- Shoot with Left Mouse or Space. Hit the red cube to score; it respawns - Shoot with Left Mouse or Space. Hit the red cube to score; it respawns
@ -24,9 +25,11 @@ make -C C/fps run
``` ```
If your distro uses different package names, install the equivalents of: If your distro uses different package names, install the equivalents of:
- libgl1, libglu1, freeglut (dev headers) - libgl1, libglu1, freeglut (dev headers)
## Notes ## Notes
- This uses old-school fixed-function OpenGL for simplicity and broad compatibility. - This uses old-school fixed-function OpenGL for simplicity and broad compatibility.
- Mouse is confined via glutWarpPointer; press M if you need to release it. - Mouse is confined via glutWarpPointer; press M if you need to release it.
- SDL2 is used only for simple procedurally generated sound effects (shoot, hit, game over). - SDL2 is used only for simple procedurally generated sound effects (shoot, hit, game over).

View File

@ -11,21 +11,21 @@
"C_Cpp.default.compilerPath": "/usr/bin/gcc", "C_Cpp.default.compilerPath": "/usr/bin/gcc",
"C_Cpp.clang_format_style": "file", "C_Cpp.clang_format_style": "file",
"C_Cpp.clang_format_fallbackStyle": "LLVM", "C_Cpp.clang_format_fallbackStyle": "LLVM",
"files.associations": { "files.associations": {
"*.h": "c", "*.h": "c",
"*.c": "c" "*.c": "c"
}, },
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.tabSize": 4, "editor.tabSize": 4,
"editor.insertSpaces": true, "editor.insertSpaces": true,
"editor.rulers": [100], "editor.rulers": [100],
"clang-tidy.executable": "clang-tidy", "clang-tidy.executable": "clang-tidy",
"clang-tidy.checks": [ "clang-tidy.checks": [
"clang-diagnostic-*", "clang-diagnostic-*",
"clang-analyzer-*", "clang-analyzer-*",
"bugprone-*", "bugprone-*",
"cert-*", "cert-*",
"misc-*", "misc-*",
@ -33,7 +33,7 @@
"portability-*", "portability-*",
"readability-*" "readability-*"
], ],
"cppcheck.enable": true, "cppcheck.enable": true,
"cppcheck.standard": ["c99"], "cppcheck.standard": ["c99"],
"cppcheck.suppress": [ "cppcheck.suppress": [

View File

@ -7,7 +7,7 @@ The imageviewer project uses secure coding practices with proper bounds checking
### Why These Warnings Appear ### Why These Warnings Appear
The static analyzer flags standard C library functions like: The static analyzer flags standard C library functions like:
- `memcpy()` - suggests using `memcpy_s()` - `memcpy()` - suggests using `memcpy_s()`
- `snprintf()` - suggests using `snprintf_s()` - `snprintf()` - suggests using `snprintf_s()`
- `strncpy()` - suggests using `strncpy_s()` - `strncpy()` - suggests using `strncpy_s()`

View File

@ -112,16 +112,16 @@ install_dependencies() {
build_imageviewer() { build_imageviewer() {
print_step "Building imageviewer..." print_step "Building imageviewer..."
# Check if we're in the right directory # Check if we're in the right directory
if [[ ! -f "main.c" ]] || [[ ! -f "Makefile" ]]; then if [[ ! -f "main.c" ]] || [[ ! -f "Makefile" ]]; then
print_error "main.c or Makefile not found. Please run this script from the imageViewer directory." print_error "main.c or Makefile not found. Please run this script from the imageViewer directory."
exit 1 exit 1
fi fi
# Clean any previous builds # Clean any previous builds
make clean 2>/dev/null || true make clean 2>/dev/null || true
# Build the project # Build the project
if make; then if make; then
print_success "Build completed successfully" print_success "Build completed successfully"
@ -129,35 +129,35 @@ build_imageviewer() {
print_error "Build failed" print_error "Build failed"
exit 1 exit 1
fi fi
# Verify the binary was created # Verify the binary was created
if [[ ! -f "imageviewer" ]]; then if [[ ! -f "imageviewer" ]]; then
print_error "imageviewer binary not found after build" print_error "imageviewer binary not found after build"
exit 1 exit 1
fi fi
print_success "imageviewer binary created" print_success "imageviewer binary created"
} }
install_binary() { install_binary() {
print_step "Installing imageviewer to ${INSTALL_DIR}..." print_step "Installing imageviewer to ${INSTALL_DIR}..."
# Create install directory if it doesn't exist # Create install directory if it doesn't exist
sudo mkdir -p "${INSTALL_DIR}" sudo mkdir -p "${INSTALL_DIR}"
# Copy the binary # Copy the binary
sudo cp imageviewer "${INSTALL_DIR}/" sudo cp imageviewer "${INSTALL_DIR}/"
sudo chmod +x "${INSTALL_DIR}/imageviewer" sudo chmod +x "${INSTALL_DIR}/imageviewer"
print_success "imageviewer installed to ${INSTALL_DIR}/imageviewer" print_success "imageviewer installed to ${INSTALL_DIR}/imageviewer"
} }
create_desktop_entry() { create_desktop_entry() {
print_step "Creating desktop entry..." print_step "Creating desktop entry..."
# Create applications directory if it doesn't exist # Create applications directory if it doesn't exist
sudo mkdir -p "${DESKTOP_FILE_DIR}" sudo mkdir -p "${DESKTOP_FILE_DIR}"
# Create desktop file # Create desktop file
sudo tee "${DESKTOP_FILE_DIR}/imageviewer.desktop" > /dev/null << EOF sudo tee "${DESKTOP_FILE_DIR}/imageviewer.desktop" > /dev/null << EOF
[Desktop Entry] [Desktop Entry]
@ -179,10 +179,10 @@ EOF
create_simple_icon() { create_simple_icon() {
print_step "Creating application icon..." print_step "Creating application icon..."
# Create icon directory if it doesn't exist # Create icon directory if it doesn't exist
sudo mkdir -p "${ICON_DIR}" sudo mkdir -p "${ICON_DIR}"
# Create a simple text-based icon (SVG) # Create a simple text-based icon (SVG)
sudo tee "${ICON_DIR}/imageviewer.svg" > /dev/null << 'EOF' sudo tee "${ICON_DIR}/imageviewer.svg" > /dev/null << 'EOF'
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
@ -201,7 +201,7 @@ EOF
update_desktop_database() { update_desktop_database() {
print_step "Updating desktop database..." print_step "Updating desktop database..."
if command -v update-desktop-database &> /dev/null; then if command -v update-desktop-database &> /dev/null; then
sudo update-desktop-database "${DESKTOP_FILE_DIR}" 2>/dev/null || true sudo update-desktop-database "${DESKTOP_FILE_DIR}" 2>/dev/null || true
print_success "Desktop database updated" print_success "Desktop database updated"
@ -212,11 +212,11 @@ update_desktop_database() {
set_default_image_viewer() { set_default_image_viewer() {
print_step "Setting imageviewer as default image viewer..." print_step "Setting imageviewer as default image viewer..."
# List of MIME types for images # List of MIME types for images
local mime_types=( local mime_types=(
"image/jpeg" "image/jpeg"
"image/jpg" "image/jpg"
"image/png" "image/png"
"image/bmp" "image/bmp"
"image/gif" "image/gif"
@ -224,34 +224,34 @@ set_default_image_viewer() {
"image/tif" "image/tif"
"image/webp" "image/webp"
) )
# Set default application for each MIME type # Set default application for each MIME type
for mime_type in "${mime_types[@]}"; do for mime_type in "${mime_types[@]}"; do
if command -v xdg-mime &> /dev/null; then if command -v xdg-mime &> /dev/null; then
xdg-mime default imageviewer.desktop "$mime_type" 2>/dev/null || true xdg-mime default imageviewer.desktop "$mime_type" 2>/dev/null || true
fi fi
done done
# Also update MIME database if available # Also update MIME database if available
if command -v update-mime-database &> /dev/null; then if command -v update-mime-database &> /dev/null; then
sudo update-mime-database /usr/share/mime 2>/dev/null || true sudo update-mime-database /usr/share/mime 2>/dev/null || true
fi fi
print_success "imageviewer set as default image viewer" print_success "imageviewer set as default image viewer"
} }
test_installation() { test_installation() {
print_step "Testing installation..." print_step "Testing installation..."
# Check if binary is in PATH # Check if binary is in PATH
if command -v imageviewer &> /dev/null; then if command -v imageviewer &> /dev/null; then
print_success "imageviewer is available in PATH" print_success "imageviewer is available in PATH"
# Show version/help # Show version/help
echo -e "${BLUE}Running imageviewer --help equivalent:${NC}" echo -e "${BLUE}Running imageviewer --help equivalent:${NC}"
echo "Usage: imageviewer <image_file_or_directory>" echo "Usage: imageviewer <image_file_or_directory>"
echo "Supported formats: JPG, JPEG, PNG, BMP, GIF, TIF" echo "Supported formats: JPG, JPEG, PNG, BMP, GIF, TIF"
# Test default application association # Test default application association
if command -v xdg-mime &> /dev/null; then if command -v xdg-mime &> /dev/null; then
local default_app=$(xdg-mime query default image/jpeg 2>/dev/null) local default_app=$(xdg-mime query default image/jpeg 2>/dev/null)
@ -261,7 +261,7 @@ test_installation() {
print_warning "Default image viewer association may not have been set correctly" print_warning "Default image viewer association may not have been set correctly"
fi fi
fi fi
else else
print_error "imageviewer not found in PATH. Installation may have failed." print_error "imageviewer not found in PATH. Installation may have failed."
exit 1 exit 1
@ -311,10 +311,10 @@ main() {
echo -e "${BLUE}ImageViewer Installation Script for Arch Linux${NC}" echo -e "${BLUE}ImageViewer Installation Script for Arch Linux${NC}"
echo "==============================================" echo "=============================================="
echo echo
check_arch check_arch
check_permissions check_permissions
# Show what the script will do # Show what the script will do
echo -e "${YELLOW}This script will:${NC}" echo -e "${YELLOW}This script will:${NC}"
echo " 1. Install SDL2 dependencies via pacman" echo " 1. Install SDL2 dependencies via pacman"
@ -323,7 +323,7 @@ main() {
echo " 4. Create a desktop entry" echo " 4. Create a desktop entry"
echo " 5. Set imageviewer as default image viewer" echo " 5. Set imageviewer as default image viewer"
echo echo
install_dependencies install_dependencies
build_imageviewer build_imageviewer
install_binary install_binary

View File

@ -1,5 +1,5 @@
# Lint script for imageViewer project
#!/bin/bash #!/bin/bash
# Lint script for imageViewer project
set -e set -e
@ -29,25 +29,25 @@ print_error() {
# Check if required tools are installed # Check if required tools are installed
check_tools() { check_tools() {
print_step "Checking required tools..." print_step "Checking required tools..."
local missing_tools=() local missing_tools=()
if ! command -v clang-tidy &> /dev/null; then if ! command -v clang-tidy &> /dev/null; then
missing_tools+=("clang-tidy") missing_tools+=("clang-tidy")
fi fi
if ! command -v cppcheck &> /dev/null; then if ! command -v cppcheck &> /dev/null; then
missing_tools+=("cppcheck") missing_tools+=("cppcheck")
fi fi
if ! command -v clang-format &> /dev/null; then if ! command -v clang-format &> /dev/null; then
missing_tools+=("clang-format") missing_tools+=("clang-format")
fi fi
if [ ${#missing_tools[@]} -ne 0 ]; then if [ ${#missing_tools[@]} -ne 0 ]; then
print_error "Missing required tools: ${missing_tools[*]}" print_error "Missing required tools: ${missing_tools[*]}"
print_step "Installing missing tools..." print_step "Installing missing tools..."
# Check if we're on Arch Linux # Check if we're on Arch Linux
if command -v pacman &> /dev/null; then if command -v pacman &> /dev/null; then
sudo pacman -S --needed clang cppcheck sudo pacman -S --needed clang cppcheck
@ -60,14 +60,14 @@ check_tools() {
exit 1 exit 1
fi fi
fi fi
print_success "All required tools are available" print_success "All required tools are available"
} }
# Run clang-tidy # Run clang-tidy
run_clang_tidy() { run_clang_tidy() {
print_step "Running clang-tidy analysis..." print_step "Running clang-tidy analysis..."
if [ -f ".clang-tidy" ]; then if [ -f ".clang-tidy" ]; then
clang-tidy main.c -- -I/usr/include/SDL2 -D_REENTRANT 2>/dev/null || { clang-tidy main.c -- -I/usr/include/SDL2 -D_REENTRANT 2>/dev/null || {
print_warning "clang-tidy found issues (see output above)" print_warning "clang-tidy found issues (see output above)"
@ -78,26 +78,26 @@ run_clang_tidy() {
print_warning "clang-tidy found issues (see output above)" print_warning "clang-tidy found issues (see output above)"
} }
fi fi
print_success "clang-tidy analysis completed" print_success "clang-tidy analysis completed"
} }
# Run cppcheck # Run cppcheck
run_cppcheck() { run_cppcheck() {
print_step "Running cppcheck analysis..." print_step "Running cppcheck analysis..."
cppcheck --enable=all --check-level=exhaustive --suppress=missingIncludeSystem \ cppcheck --enable=all --check-level=exhaustive --suppress=missingIncludeSystem \
--quiet --std=c23 main.c || { --quiet --std=c23 main.c || {
print_warning "cppcheck found issues (see output above)" print_warning "cppcheck found issues (see output above)"
} }
print_success "cppcheck analysis completed" print_success "cppcheck analysis completed"
} }
# Check code formatting # Check code formatting
check_formatting() { check_formatting() {
print_step "Checking code formatting..." print_step "Checking code formatting..."
if [ -f ".clang-format" ]; then if [ -f ".clang-format" ]; then
if clang-format --dry-run --Werror main.c 2>/dev/null; then if clang-format --dry-run --Werror main.c 2>/dev/null; then
print_success "Code formatting is correct" print_success "Code formatting is correct"
@ -113,7 +113,7 @@ check_formatting() {
# Run basic compile check # Run basic compile check
compile_check() { compile_check() {
print_step "Running compile check..." print_step "Running compile check..."
# Try to compile with extra warnings # Try to compile with extra warnings
if gcc -Wall -Wextra -Wpedantic -std=c99 -O2 \ if gcc -Wall -Wextra -Wpedantic -std=c99 -O2 \
$(pkg-config --cflags sdl2 2>/dev/null || echo "-I/usr/include/SDL2") \ $(pkg-config --cflags sdl2 2>/dev/null || echo "-I/usr/include/SDL2") \
@ -132,27 +132,27 @@ compile_check() {
# Check for common C issues # Check for common C issues
check_common_issues() { check_common_issues() {
print_step "Checking for common C issues..." print_step "Checking for common C issues..."
local issues=0 local issues=0
# Check for TODO/FIXME comments # Check for TODO/FIXME comments
if grep -n "TODO\|FIXME\|XXX\|HACK" main.c 2>/dev/null; then if grep -n "TODO\|FIXME\|XXX\|HACK" main.c 2>/dev/null; then
print_warning "Found TODO/FIXME comments" print_warning "Found TODO/FIXME comments"
issues=$((issues + 1)) issues=$((issues + 1))
fi fi
# Check for potential buffer overflows # Check for potential buffer overflows
if grep -n "strcpy\|strcat\|sprintf\|gets" main.c 2>/dev/null; then if grep -n "strcpy\|strcat\|sprintf\|gets" main.c 2>/dev/null; then
print_warning "Found potentially unsafe string functions" print_warning "Found potentially unsafe string functions"
issues=$((issues + 1)) issues=$((issues + 1))
fi fi
# Check for magic numbers (basic check) # Check for magic numbers (basic check)
if grep -E "\b[0-9]{3,}\b" main.c | grep -v "printf\|#define" 2>/dev/null; then if grep -E "\b[0-9]{3,}\b" main.c | grep -v "printf\|#define" 2>/dev/null; then
print_warning "Found potential magic numbers" print_warning "Found potential magic numbers"
issues=$((issues + 1)) issues=$((issues + 1))
fi fi
if [ $issues -eq 0 ]; then if [ $issues -eq 0 ]; then
print_success "No common issues found" print_success "No common issues found"
fi fi
@ -163,31 +163,31 @@ main() {
echo -e "${BLUE}C Language Linter for imageViewer Project${NC}" echo -e "${BLUE}C Language Linter for imageViewer Project${NC}"
echo "==========================================" echo "=========================================="
echo echo
# Check if we're in the right directory # Check if we're in the right directory
if [ ! -f "main.c" ]; then if [ ! -f "main.c" ]; then
print_error "main.c not found. Please run this script from the imageViewer directory." print_error "main.c not found. Please run this script from the imageViewer directory."
exit 1 exit 1
fi fi
check_tools check_tools
echo echo
compile_check compile_check
echo echo
run_clang_tidy run_clang_tidy
echo echo
run_cppcheck run_cppcheck
echo echo
check_formatting check_formatting
echo echo
check_common_issues check_common_issues
echo echo
print_success "Linting completed!" print_success "Linting completed!"
echo echo
echo -e "${BLUE}Available commands:${NC}" echo -e "${BLUE}Available commands:${NC}"

View File

@ -1415,4 +1415,4 @@ static int save_processed_image(const ImageViewer *viewer) {
printf("Saved %s image: %s\n", any_trim ? "trimmed" : "rotated", out_path); printf("Saved %s image: %s\n", any_trim ? "trimmed" : "rotated", out_path);
} }
return 1; return 1;
} }

View File

@ -34,7 +34,7 @@ print_error() {
remove_files() { remove_files() {
print_step "Removing imageviewer files..." print_step "Removing imageviewer files..."
# Remove binary # Remove binary
if [[ -f "${INSTALL_DIR}/imageviewer" ]]; then if [[ -f "${INSTALL_DIR}/imageviewer" ]]; then
sudo rm "${INSTALL_DIR}/imageviewer" sudo rm "${INSTALL_DIR}/imageviewer"
@ -42,7 +42,7 @@ remove_files() {
else else
print_warning "Binary not found at ${INSTALL_DIR}/imageviewer" print_warning "Binary not found at ${INSTALL_DIR}/imageviewer"
fi fi
# Remove desktop entry # Remove desktop entry
if [[ -f "${DESKTOP_FILE_DIR}/imageviewer.desktop" ]]; then if [[ -f "${DESKTOP_FILE_DIR}/imageviewer.desktop" ]]; then
sudo rm "${DESKTOP_FILE_DIR}/imageviewer.desktop" sudo rm "${DESKTOP_FILE_DIR}/imageviewer.desktop"
@ -50,7 +50,7 @@ remove_files() {
else else
print_warning "Desktop entry not found" print_warning "Desktop entry not found"
fi fi
# Remove icon # Remove icon
if [[ -f "${ICON_DIR}/imageviewer.svg" ]]; then if [[ -f "${ICON_DIR}/imageviewer.svg" ]]; then
sudo rm "${ICON_DIR}/imageviewer.svg" sudo rm "${ICON_DIR}/imageviewer.svg"
@ -62,11 +62,11 @@ remove_files() {
reset_default_associations() { reset_default_associations() {
print_step "Resetting default image viewer associations..." print_step "Resetting default image viewer associations..."
# List of MIME types for images # List of MIME types for images
local mime_types=( local mime_types=(
"image/jpeg" "image/jpeg"
"image/jpg" "image/jpg"
"image/png" "image/png"
"image/bmp" "image/bmp"
"image/gif" "image/gif"
@ -74,7 +74,7 @@ reset_default_associations() {
"image/tif" "image/tif"
"image/webp" "image/webp"
) )
# Reset default application for each MIME type # Reset default application for each MIME type
for mime_type in "${mime_types[@]}"; do for mime_type in "${mime_types[@]}"; do
if command -v xdg-mime &> /dev/null; then if command -v xdg-mime &> /dev/null; then
@ -89,13 +89,13 @@ reset_default_associations() {
fi fi
fi fi
done done
print_success "Default image viewer associations reset" print_success "Default image viewer associations reset"
} }
update_desktop_database() { update_desktop_database() {
print_step "Updating desktop database..." print_step "Updating desktop database..."
if command -v update-desktop-database &> /dev/null; then if command -v update-desktop-database &> /dev/null; then
sudo update-desktop-database "${DESKTOP_FILE_DIR}" 2>/dev/null || true sudo update-desktop-database "${DESKTOP_FILE_DIR}" 2>/dev/null || true
print_success "Desktop database updated" print_success "Desktop database updated"
@ -108,7 +108,7 @@ main() {
echo -e "${BLUE}ImageViewer Uninstallation Script${NC}" echo -e "${BLUE}ImageViewer Uninstallation Script${NC}"
echo "=================================" echo "================================="
echo echo
# Show what will be removed # Show what will be removed
echo -e "${YELLOW}This script will remove:${NC}" echo -e "${YELLOW}This script will remove:${NC}"
echo " - ${INSTALL_DIR}/imageviewer" echo " - ${INSTALL_DIR}/imageviewer"
@ -117,11 +117,11 @@ main() {
echo echo
echo -e "${YELLOW}Note: Dependencies (SDL2 libraries) will NOT be removed.${NC}" echo -e "${YELLOW}Note: Dependencies (SDL2 libraries) will NOT be removed.${NC}"
echo echo
remove_files remove_files
reset_default_associations reset_default_associations
update_desktop_database update_desktop_database
echo echo
echo -e "${GREEN}ImageViewer has been successfully uninstalled!${NC}" echo -e "${GREEN}ImageViewer has been successfully uninstalled!${NC}"
echo echo

View File

@ -747,4 +747,4 @@ int main(int argc, char **argv)
printf("}\n"); printf("}\n");
free(arr); free(arr);
return 0; return 0;
} }

View File

@ -69,4 +69,4 @@ int in_check(const Position *pos, Color side);
int gen_moves(const Position *pos, Move *moves, int max_moves, int captures_only); int gen_moves(const Position *pos, Move *moves, int max_moves, int captures_only);
int gen_moves_pseudo(const Position *pos, Move *moves, int max_moves, int captures_only); int gen_moves_pseudo(const Position *pos, Move *moves, int max_moves, int captures_only);
#endif // MOVEGEN_H #endif // MOVEGEN_H

View File

@ -21,4 +21,4 @@ int evaluate(const Position *pos);
// Negamax alpha-beta returning score in centipawns from side-to-move perspective. // Negamax alpha-beta returning score in centipawns from side-to-move perspective.
int alphabeta(Position pos, int depth, int alpha, int beta, PrincipalVariation *pv); int alphabeta(Position pos, int depth, int alpha, int beta, PrincipalVariation *pv);
#endif // SEARCH_H #endif // SEARCH_H

View File

@ -1,12 +1,12 @@
#include <stdio.h> #include <stdio.h>
const int NUMBER_FOR_POLISH_SMALL_L = 136; const int NUMBER_FOR_POLISH_SMALL_L = 136;
int main(void) int main(void)
{ {
for (char i = 'a'; i < 'z' + 1; ++i) for (char i = 'a'; i < 'z' + 1; ++i)
{ {
printf("%ca%cka\n", i, NUMBER_FOR_POLISH_SMALL_L); printf("%ca%cka\n", i, NUMBER_FOR_POLISH_SMALL_L);
} }
return 0; return 0;
} }

4
C/misc/randomJPG/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.jpeg
*.jpg
generated_images*/*
generate_images

View File

@ -0,0 +1,6 @@
Did you ever need to generate random jpg images with huge file size? Now you can! \\
Compilation: Install libjpeg-dev \\
sudo apt-get install libjpeg-dev \\
Run make \\
make \\
Run ./generate_images

1
C/misc/split/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
split

View File

@ -18,9 +18,11 @@ Run:
``` ```
Tips: Tips:
- ESC clears selection. - ESC clears selection.
- Press `m` to cycle to a stored mistake position and practice the best move there. - Press `m` to cycle to a stored mistake position and practice the best move there.
- If you play Black, the board flips so Black is at the bottom. - If you play Black, the board flips so Black is at the bottom.
Notes: Notes:
- Rendering avoids TTF dependency; pieces are clear, high-contrast geometric glyphs. - Rendering avoids TTF dependency; pieces are clear, high-contrast geometric glyphs.

65
C/scrapeWebsite/.gitignore vendored Normal file
View File

@ -0,0 +1,65 @@
# JPEG
*.jpg
*.jpeg
*.jpe
*.jif
*.jfif
*.jfi
# JPEG 2000
*.jp2
*.j2k
*.jpf
*.jpx
*.jpm
*.mj2
# JPEG XR
*.jxr
*.hdp
*.wdp
# Graphics Interchange Format
*.gif
# RAW
*.raw
# Web P
*.webp
# Portable Network Graphics
*.png
# Animated Portable Network Graphics
*.apng
# Multiple-image Network Graphics
*.mng
# Tagged Image File Format
*.tiff
*.tif
# Scalable Vector Graphics
*.svg
*.svgz
# Portable Document Format
*.pdf
# X BitMap
*.xbm
# BMP
*.bmp
*.dib
# ICO
*.ico
# 3D Images
*.3dm
*.max
scrape

View File

@ -1,10 +1,10 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <windows.h> #include <windows.h>
int main() int main()
{ {
printf("Henlo\n"); printf("Henlo\n");
sleep(20); sleep(20);
return 0; return 0;
} }

5
CPP/SFMLEngine/readme.md Normal file
View File

@ -0,0 +1,5 @@
# Textures and Audio used:
## Space Game Starter Set by [hc](https://opengameart.org/users/hc) - https://opengameart.org/content/space-game-starter-set
## 100 seamless textures by [Mitch Featherston](http://pdtextures.blogspot.com/) (Submitted by [Clint Bellanger](https://opengameart.org/users/clint-bellanger) - https://opengameart.org/content/100-seamless-textures

View File

@ -0,0 +1,25 @@
#include <math.h>
#include <iostream>
#include <iomanip>
const unsigned long long int ITERATIONS = 10000;
long double getPi()
{
long double pi = 4;
bool negative = 1;
for(unsigned int i = 3; i < ITERATIONS; i += 2)
{
if(negative) pi -= 4.0 / i;
else pi += 4.0 / i;
negative = !negative;
}
std::cout << std::setprecision(2000) << pi << std::endl;
return pi;
}
int main()
{
getPi();
return 0;
}

View File

@ -0,0 +1,410 @@
#include <iostream>
#include <vector>
const std::vector <std::string> ATUTY = {"BA", "Trefl", "Karo", "Kier", "Pik"};
const bool A_ID = 0;
const bool B_ID = 1;
const std::vector <std::string> GRACZE = {};
const std::vector <std::string> PO_PARTII {"Nikt", GRACZE[A_ID], GRACZE[B_ID], "Obaj Gracze"};
const int DOMYSLNE_LEWY = 6;
const int BEZ_ATUTU_ID = 1;
const int TREFL_ID = 2;
const int KARO_ID = 3;
const int KIER_ID = 4;
const int PIK_ID = 5;
const int SZLEMIK = 6;
const int SZLEM = 7;
const int CYKL_PO_PARTII = 4;
const int MAKSYMALNY_LEW = 7;
const int MINIMALNY_LEW = 1;
const int ILOSC_LEW = 13;
void print(const std::string s)
{
std::cout << s << std::endl;
}
void tabela(std::vector <int> punktyA, std::vector <int> punktyB)
{
std::cout << "Numer Gry" << " Po Partii" << " " << GRACZE[A_ID] << " " << GRACZE[B_ID] << std::endl;
for(int i = 0; i < punktyA.size(); i++)
{
std::cout << i + 1 << " " << PO_PARTII[i % CYKL_PO_PARTII] << " " << punktyA[i] << " " << punktyB[i] << std::endl;
}
}
void lwyAtut(int lwy, int atut)
{
if(lwy == SZLEMIK)
{
print("Wybrano szlemik!");
return;
}
if(lwy == SZLEM)
{
print("Wybrano szlema!");
return;
}
std::cout << "Wybrano kontrakt: " << lwy << " " << ATUTY[atut - 1] << std::endl;
}
int zagraneLwy()
{
int lwy;
bool flagaLwy;
do
{
flagaLwy = 0;
print("Ile lew?");
char lwyC;
std::cin >> lwyC;
lwy = lwyC - '0';
if(lwy < MINIMALNY_LEW)
{
print("Podales za malo lew!");
flagaLwy = 1;
}
if(lwy > MAKSYMALNY_LEW)
{
print("Podales za duzo lew!");
flagaLwy = 1;
}
}while(flagaLwy);
return lwy;
}
int zagranyAtut(int lwy)
{
int atut;
bool flagaAtut;
if(lwy > 6) return 1;
do
{
flagaAtut = 0;
print("Jaki atut?");
print("1 - BA");
print("2 - Trefl");
print("3 - Karo");
print("4 - Kier");
print("5 - Pik");
char atutC;
std::cin >> atutC;
atut = atutC - '0';
if(atut < 1 || atut > 5)
{
print("Wybrales zla liczbe!");
flagaAtut = 1;
}
}while(flagaAtut);
return atut;
}
bool zagranaKontra()
{
char kontraC = '0';
print("Czy zostala zagrana kontra?");
print("1 - TAK");
print("0 - NIE");
std::cin >> kontraC;
bool kontraBool = kontraC - '0';
return kontraBool;
}
bool zagranaRekontra()
{
char rekontraC = '0';
print("Czy zostala zagrana rekontra?");
print("1 - TAK");
print("0 - NIE");
std::cin >> rekontraC;
bool rekontraBool = rekontraC - '0';
return rekontraBool;
}
void stanGry(int lwy, int atut, bool kontraBool, bool rekontraBool, int ktoraGra, int ktoKontrakt)
{
std::cout << "Kontrakt Wygrali: " << GRACZE[ktoKontrakt] << std::endl;
lwyAtut(lwy, atut);
if(kontraBool)
{
if(rekontraBool) print("Zostala zagrana REkontra!");
else print("Zostala zagrana Kontra!");
}
std::cout << "Po partii sa: " << PO_PARTII[ktoraGra % 4] << std::endl;
}
int ktoKontrakt()
{
char ktoKontraktC;
print("Kto wygral Kontrakt?");
std::cout << "1. " << GRACZE[A_ID] << std::endl;
std::cout << "2. " << GRACZE[B_ID] << std::endl;
std::cin >> ktoKontraktC;
int ktoKontraktI = ktoKontraktC - '1';
std::cout << "ktoKontraktI " << ktoKontraktI;
return ktoKontraktI;
}
int ileWpadek()
{
std::string ileWpadekS;
print("ile lew wygrali obroncy?");
std::cin >> ileWpadekS;
int ileWpadek = stoi(ileWpadekS);
return ileWpadek;
}
void punkty(std::vector <int> &punktyA, std::vector <int> &punktyB, int lwy, int atut, bool kontraBool, bool rekontraBool,
int ktoraGra, int ktoKontraktI, bool rozgrywajacyWygral, int wpadki)
{
int sumaPunktow = 0;
if(rozgrywajacyWygral)
{
int zdobyteLewy = ILOSC_LEW - wpadki - DOMYSLNE_LEWY;
int nadrobki = zdobyteLewy - lwy;
int punktyZaLew;
std::cout << "wartosc kontraBool: " << kontraBool << "; wartosc rekontraBool: " << rekontraBool << std::endl;
// Lewy Deklarowane
if(atut == TREFL_ID || atut == KARO_ID)
{
print("kontrakt TREFL lub KARO kazda karta kontraktowa za 20");
punktyZaLew = 20;
if(kontraBool)
{
print("kontra TREFL lub KARO, kazda karta kontraktowa za 40");
punktyZaLew = 40;
}
if(rekontraBool)
{
print("rekontra TREFL lub KARO, kazda karta kontraktowa za 80");
punktyZaLew = 80;
}
std::cout << "Ilosc lew w kontrakcie: " << lwy << " do punktow dodaje sie " << lwy * punktyZaLew << std::endl;
sumaPunktow += (lwy * punktyZaLew);
}
if(atut == KIER_ID || atut == PIK_ID)
{
print("kontrakt KIER lub PIK, kazda kontraktowa 30");
punktyZaLew = 30;
if(kontraBool)
{
print("kontra KIER lub PIK, kazda kontraktowa za 60");
punktyZaLew = 60;
}
if(rekontraBool)
{
print("rekontra KIER lub PIK, kazda kontraktowa za 120");
punktyZaLew = 120;
}
std::cout << "Ilosc lew w kontrakcie: " << lwy << " do punktow dodaje sie " << lwy * punktyZaLew << std::endl;
sumaPunktow += (lwy * punktyZaLew);
}
if(atut == BEZ_ATUTU_ID)
{
punktyZaLew = 30;
print("kontrakt BEZ_ATUTU, pierwsza lewa za 40, kazda nastepna za 30");
sumaPunktow = 40;
if(kontraBool)
{
print("kontrakt BEZ_ATUTU, pierwsza lewa za 80, kazda nastepna za 60");
sumaPunktow = 80;
punktyZaLew = 60;
}
if(rekontraBool)
{
print("kontrakt BEZ_ATUTU, pierwsza lewa za 160, kazda nastepna za 120");
sumaPunktow = 160;
punktyZaLew = 120;
}
sumaPunktow += ( (lwy - 1) * punktyZaLew);
}
bool czyRozgrywajacyPoPartii = ( ( (ktoraGra % CYKL_PO_PARTII) - 1) == ktoKontraktI || ktoraGra % CYKL_PO_PARTII == 3);
if(lwy == SZLEMIK)
{
if(czyRozgrywajacyPoPartii) sumaPunktow += 750;
else sumaPunktow += 500;
}
if(lwy == SZLEM)
{
if(czyRozgrywajacyPoPartii) sumaPunktow += 1500;
else sumaPunktow += 1000;
}
bool dograna = (sumaPunktow >= 100);
if(dograna)
{
if(czyRozgrywajacyPoPartii) sumaPunktow += 500;
else sumaPunktow += 300;
}else sumaPunktow += 50;
// Nadrobki
if(!kontraBool && !rekontraBool)
{
int punktyZaNadrobki = punktyZaLew;
sumaPunktow += nadrobki * punktyZaNadrobki;
}
if(kontraBool && !rekontraBool)
{
int punktyZaNadrobki = 100;
if(czyRozgrywajacyPoPartii) punktyZaNadrobki = 200;
sumaPunktow += nadrobki * punktyZaNadrobki;
}
if(kontraBool && rekontraBool)
{
int punktyZaNadrobki = 200;
if(czyRozgrywajacyPoPartii) punktyZaNadrobki = 400;
sumaPunktow += nadrobki * punktyZaNadrobki;
}
if(kontraBool && !rekontraBool) sumaPunktow += 50;
if(kontraBool && rekontraBool) sumaPunktow += 100;
std::cout << "Rozgrywajacy zdobyl: " << sumaPunktow << std::endl;
if(ktoKontraktI == A_ID)
{
punktyA.push_back(sumaPunktow);
punktyB.push_back(0);
}
else
{
punktyB.push_back(sumaPunktow);
punktyA.push_back(0);
}
return;
}else
{
int zebraneLewy = ILOSC_LEW - wpadki;
int lewyWpadkowe = (lwy + DOMYSLNE_LEWY) - zebraneLewy;
int sumaPunktow = 0;
bool broniacyPoPartii = ( ((ktoraGra % CYKL_PO_PARTII) - 1) == !ktoKontraktI || ktoraGra % CYKL_PO_PARTII == 3);
if(broniacyPoPartii)
{
if(!kontraBool && !rekontraBool)
{
sumaPunktow = 100;
for(int i = 1; i < lewyWpadkowe; i++)
{
if(i < 4) sumaPunktow += 100;
else sumaPunktow += 0;
}
}
if(kontraBool && !rekontraBool)
{
sumaPunktow = 200;
for(int i = 1; i < lewyWpadkowe; i++)
{
if(i < 4) sumaPunktow += 300;
else sumaPunktow += 0;
}
}
if(kontraBool && rekontraBool)
{
sumaPunktow = 400;
for(int i = 1; i < lewyWpadkowe; i++)
{
if(i < 4) sumaPunktow += 600;
else sumaPunktow += 0;
}
}
}else
{
if(!kontraBool && !rekontraBool)
{
sumaPunktow = 50;
for(int i = 1; i < lewyWpadkowe; i++)
{
if(i < 4) sumaPunktow += 50;
else sumaPunktow += 0;
}
}
if(kontraBool && !rekontraBool)
{
sumaPunktow = 100;
for(int i = 1; i < lewyWpadkowe; i++)
{
if(i < 4) sumaPunktow += 200;
else sumaPunktow += 100;
}
}
if(kontraBool && rekontraBool)
{
sumaPunktow = 200;
for(int i = 1; i < lewyWpadkowe; i++)
{
if(i < 4) sumaPunktow += 400;
else sumaPunktow += 200;
}
}
}
std::cout << "Broniacy zdobyli: " << sumaPunktow << std::endl;
if(ktoKontraktI == A_ID)
{
punktyB.push_back(sumaPunktow);
punktyA.push_back(0);
}
else
{
punktyA.push_back(sumaPunktow);
punktyB.push_back(0);
}
return;
}
}
bool gra()
{
bool koniecGry = 0;
std::vector <int> punktyA;
std::vector <int> punktyB;
do{
int ktoraGra = 0;
tabela(punktyA, punktyB);
int ktoKontraktI = ktoKontrakt();
int lwy = zagraneLwy();
int atut = zagranyAtut(lwy);
bool kontraBool = zagranaKontra();
bool rekontraBool = 0;
if(kontraBool) rekontraBool = zagranaRekontra();
stanGry(lwy, atut, kontraBool, rekontraBool, ktoraGra, ktoKontraktI);
int wpadki = ileWpadek();
int zebraneLewy = ILOSC_LEW - wpadki;
bool rozgrywajacyWygral = 1;
if( zebraneLewy >= lwy + DOMYSLNE_LEWY) rozgrywajacyWygral = 1;
else rozgrywajacyWygral = 0;
punkty(punktyA, punktyB, lwy, atut, kontraBool, rekontraBool, ktoraGra, ktoKontraktI, rozgrywajacyWygral, wpadki);
print("Czy koniec gry? 1 - TAK, 0 - NIE");
std::cin >> koniecGry;
}while(!koniecGry);
tabela(punktyA, punktyB);
return 0;
}
int main()
{
while(gra());
return 0;
}

View File

@ -0,0 +1,91 @@
#ifndef BASIC_CPP
#define BASIC_CPP
#include <string>
#include <vector>
#include <iostream>
#include <windows.h>
void print(const std::string s) { std::cout << s << std::endl; }
void printErrorStringContainsNotNumber(const std::string s)
{
std::cout << "string: \"" << s
<< "\" contains character different than number " << std::endl;
}
void printNumberTooLow(const int number, const int min)
{
std::cout << "number: " << number
<< " is too low. Minimal number is: " << min << std::endl;
}
void printNumberTooHigh(const int number, const int max)
{
std::cout << "number: " << number
<< " is too high. Maximal number is: " << max << std::endl;
}
void printNotValidStringLength(const std::string s, const int desiredLength)
{
std::cout << "String: \"" << s << "\" is too short/too long, it is: "
<< s.length() << " characters long but should be: " << desiredLength
<< " characters long " << std::endl;
}
void printInvalidCharacter(const char c, const char desiredCharacter)
{
std::cout << "[ " << c << " ] Is invalid character, expected: [ "
<< desiredCharacter << " ]" << std::endl;
}
void printContainsIllegalCharacter( const std::string s,
const char illegalCharacter )
{
std::cout << "String: " << s << " consists of illegal sign: ["
<< illegalCharacter << "]!" << std::endl;
}
bool numberTooLow(const int number, const int min)
{
if(number < min)
{
printNumberTooLow(number, min);
return 1;
}
return 0;
}
bool numberTooHigh(const int number, const int max)
{
if(number > max)
{
printNumberTooHigh(number, max);
return 1;
}
return 0;
}
bool containsIllegalCharacter(const std::string s, const char illegalCharacter)
{
if( s.find(illegalCharacter) != std::string::npos)
{
printContainsIllegalCharacter(s, illegalCharacter);
return 1;
}
return 0;
}
void e()
{
print("Poor man breakboint");
}
bool charIsNumber(const char c)
{
return c >= '0' && c <= '9';
}
#endif

View File

@ -0,0 +1,89 @@
#ifndef MAIN_CPP
#define MAIN_CPP
#include <iostream>
#include <string>
#include <vector>
#include "basic.cpp"
std::vector <int> fillVector(const int min, const int max)
{
std::vector <int> newVector;
for(int i = min; i <= max; i++)
{
newVector.push_back(i);
}
return newVector;
}
const int MAX_SPOT = 20;
const int MIN_SPOT = 1;
const std::vector <int> NORMAL_POINTS = fillVector(MIN_SPOT, MAX_SPOT);
std::vector <int> multiplyVector(const std::vector <int> v, int multiplyBy)
{
std::vector <int> newVector;
for(unsigned int i = 0; i < v.size(); i++)
{
newVector.push_back(v.at(i)*multiplyBy);
}
return newVector;
}
const std::vector <int> DOUBLE_POINTS = multiplyVector(NORMAL_POINTS, 2);
const std::vector <int> TRIPLE_POINTS = multiplyVector(NORMAL_POINTS, 3);
const int MAX_ONE_HIT = TRIPLE_POINTS.at(TRIPLE_POINTS.size() - 1);
const int THROWS_IN_ONE_HIT = 3;
const int MAX_POINTS_TURN = THROWS_IN_ONE_HIT * MAX_ONE_HIT;
const int STARTING_POINTS = 501;
const int FINAL_POINTS = 0;
bool validString(const std::string s)
{
for(unsigned int i = 0; i < s.length(); i++)
{
if(!charIsNumber(s.at(i)))
{
printErrorStringContainsNotNumber(s);
return 0;
}
}
return 1;
}
bool validNumberInput(const std::string input, const int min, const int max)
{
if(!validString(input)) return 0;
int inputInt = std::stoi(input);
if(numberTooLow(inputInt, min)) return 0;
if(numberTooHigh(inputInt, max)) return 0;
return 1;
}
bool validInput(const std::string s)
{
if(s.length() > 3) return 0;
if(!validNumberInput(s, FINAL_POINTS, STARTING_POINTS)) return 0;
return 1;
}
std::vector <int> requiredShoots(const int pointsLeft)
{
}
int main()
{
print("Enter points left: ");
std::string pointsLeft;
do{
getline(std::cin, pointsLeft);
}while(!validInput(pointsLeft));
int pointsLeftInt = std::stoi(pointsLeft);
requiredShoots(pointsLeftInt);
return 0;
}
#endif

View File

@ -0,0 +1,60 @@
#include <iostream>
#include <cmath>
const int PERCENTAGE = 44;
const float PERCENTAGE_DENOMINATOR = 100;
const int NUMBERS_TO_CHECK = 200;
const bool DEBUG = 0;
bool isInteger(const float number)
{
return number == std::floor(number);
}
void printIsInteger(const int i, const int inputPercentage,
const float calculatedPercentage)
{
std::cout << i << "*" << inputPercentage << "% is an integer number: "
<< i*calculatedPercentage << std::endl;
}
void printDebug(const int i, const float calculatedPercentage)
{
std::cout << "i = " << i << std::endl;
std::cout << i*calculatedPercentage << std::endl;
}
void isIntegerLoop( const bool debugOn = DEBUG,
const int maxNumber = NUMBERS_TO_CHECK,
const int inputPercentage = PERCENTAGE )
{
float actualPercentage = inputPercentage / PERCENTAGE_DENOMINATOR;
for(int i = 1; i <= maxNumber; i++)
{
if(isInteger(i*actualPercentage))
{
printIsInteger(i, inputPercentage, actualPercentage);
}
else if(debugOn) printDebug(i, actualPercentage);
}
}
void printStartingMessage(const int maxNumber = NUMBERS_TO_CHECK,
const int inputPercentage = PERCENTAGE)
{
std::cout << "For max number = " << maxNumber
<< "; and a percentage: " << inputPercentage
<< "; Found following integer numbers: " << std::endl;
}
void mainFunctions()
{
printStartingMessage();
isIntegerLoop();
}
int main()
{
mainFunctions();
return 0;
}

View File

@ -0,0 +1,133 @@
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <fstream>
#ifndef CHECK_ISBN_CPP
#define CHECK_ISBN_CPP
const bool DEBUG = 0;
const int ISBN_LENGTH = 10;
const int CHECK_NUMBER = 11;
const unsigned long long int HIGHEST_ISBN = 9999999999;
void printVector(std::vector <int> v)
{
for(unsigned int i = 0; i < v.size(); i++)
{
std::cout << v[i] << "; ";
}
}
void print(const std::string printMe)
{
std::cout << printMe << std::endl;
}
void e()
{
print("PRINT");
}
bool checkInput(const std::string input)
{
if(input.length() != ISBN_LENGTH)
{
print("Your number is too short/too long");
return 0;
}
for(int i = 0; i <= ISBN_LENGTH - 1; i++)
{
if(input.at(i) < '0' || input.at(i) > '9')
{
print("Your number consists of illegal characters");
return 0;
}
}
return 1;
}
std::vector <int> stringToIntVector(const std::string input)
{
std::vector <int> vector;
for(int i = input.length() - 1; i >= 0; i--)
{
vector.push_back(input.at(i) - '0');
}
return vector;
}
std::vector <int> userISBN()
{
std::string input;
do{
std::cout << "Enter the ISBN number (10 digits): ";
getline(std::cin, input);
}while(!checkInput(input));
return stringToIntVector(input);
}
bool checkISBN(const std::vector <int> isbn)
{
int sum = 0, t = 0;
for(int i = 0; i < ISBN_LENGTH; i++)
{
t += isbn[i];
sum += t;
}
/*if(DEBUG)
{
if(!(sum % CHECK_NUMBER)) print("^^^ VALID NUMBER ^^^");
} */
return !(sum % CHECK_NUMBER);
}
std::vector <int> intToVector(unsigned long long int number)
{
std::vector <int> numbers;
while(number > 0)
{
numbers.push_back(number % 10);
number /= 10;
}
std::reverse(numbers.begin(), numbers.end());
return numbers;
}
int checkAll()
{
int sum = 0;
std::ofstream file;
file.open("ISBN.txt");
for(unsigned long long int i = HIGHEST_ISBN; i >= 1; i--)
{
//if(DEBUG) std::cout << i << std::endl;
if( checkISBN(intToVector(i)) )
{
++sum;
file << std::to_string(i) << "\n";
}
}
file << "There are " << sum << " valid ISBN numbers\n";
file.close();
return sum;
}
int main()
{
checkAll();
return 0;
}
#endif

View File

@ -0,0 +1,48 @@
#include <iostream>
#include <vector>
struct charOccurence
{
char c;
int occurrence;
};
void printCharOccurenceVector(const std::vector <charOccurence> v)
{
std::cout << "[";
for(unsigned int i = 0; i < v.size(); i++)
{
std::cout << "(\"" << v.at(i).c << "\", " << v.at(i).occurrence << ")" << (i + 1 == v.size() ? "" : ", ");
}
std::cout << "]" << std::endl;
}
int main()
{
std::vector <charOccurence> list;
std::string userInput = "aaaabbbcca";
charOccurence newCharOccurence;
newCharOccurence.c = userInput.at(0);
newCharOccurence.occurrence = 1;
for(unsigned int i = 1, j = 1; i < userInput.length(); i++)
{
char newCharacter = userInput.at(i);
if(newCharacter != newCharOccurence.c)
{
list.push_back(newCharOccurence);
j = 1;
newCharOccurence.c = newCharacter;
newCharOccurence.occurrence = j;
}else
{
newCharOccurence.occurrence++;
}
}
list.push_back(newCharOccurence);
printCharOccurenceVector(list);
return 0;
}

View File

@ -0,0 +1,177 @@
#ifndef BASIC_CPP
#define BASIC_CPP
#include <string>
#include <vector>
#include <iostream>
#include <fstream>
void print(const std::string s) { std::cout << s << std::endl; }
int charToInt(const char c) { return c - '0'; }
void e() { print("Poor man breakboint"); }
bool charIsNumber(const char c) { return c >= '0' && c <= '9'; }
void printStringNewLine(const std::string s)
{
std::cout << "string: " << std::endl;
std::cout << "\"" << s << "\"" << std::endl;
}
void printStringContainsNotNumbers(const std::string s, const int position)
{
printStringNewLine(s);
std::cout << "contains character different than number at position: " << position
<< "; this character is: " << s.at(position) << std::endl;
}
void printStringContainsNumbers(const std::string s, const int position)
{
printStringNewLine(s);
std::cout << "contains number at postion: " << position
<< "; this number is: " << s.at(position) << std::endl;
}
void printNumberTooLow(const int number, const int min)
{
std::cout << "number: " << number
<< " is too low. Minimal number is: " << min << std::endl;
}
void printNumberTooHigh(const int number, const int max)
{
std::cout << "number: " << number
<< " is too high. Maximal number is: " << max << std::endl;
}
void printNotValidStringLength(const std::string s, const int desiredLength)
{
printStringNewLine(s);
std::cout << "is too short/too long, it is: "
<< s.length() << " characters long but should be: " << desiredLength
<< " characters long " << std::endl;
}
void printInvalidCharacter(const char c, const char desiredCharacter)
{
std::cout << "[ " << c << " ] Is invalid character, expected: [ "
<< desiredCharacter << " ]" << std::endl;
}
void printContainsIllegalCharacter( const std::string s,
const char illegalCharacter )
{
printStringNewLine(s);
std::cout << " consists of illegal sign: ["
<< illegalCharacter << "]!" << std::endl;
}
bool numberTooLow(const int number, const int min)
{
if(number < min)
{
printNumberTooLow(number, min);
return 1;
}
return 0;
}
bool numberTooHigh(const int number, const int max)
{
if(number > max)
{
printNumberTooHigh(number, max);
return 1;
}
return 0;
}
bool containsIllegalCharacter(const std::string s, const char illegalCharacter)
{
if( s.find(illegalCharacter) != std::string::npos)
{
printContainsIllegalCharacter(s, illegalCharacter);
return 1;
}
return 0;
}
void printStringVector(const std::vector <std::string> vector)
{
for(unsigned int i = 0; i < vector.size(); i++) print(vector.at(i));
}
bool stringContainsNotNumbers(const std::string s)
{
for(unsigned int i = 0; i < s.length(); i++)
{
if(!charIsNumber(s.at(i)))
{
printStringContainsNotNumbers(s, i);
return 1;
}
}
return 0;
}
bool stringContainsNumbers(const std::string s)
{
for(unsigned int i = 0; i < s.length(); i++)
{
if(charIsNumber(s.at(i)))
{
printStringContainsNumbers(s, i);
return 1;
}
}
return 0;
}
bool validStringLength(const std::string s, const int desiredLength)
{
int stringLength = s.length();
if(stringLength != desiredLength)
{
printNotValidStringLength(s, desiredLength);
return 0;
}
return 1;
}
bool validCharacter(const char inputC, const char desiredC)
{
if(inputC != desiredC)
{
printInvalidCharacter(inputC, desiredC);
return 0;
}
return 1;
}
void vectorToFile(const std::vector <std::string> strings, std::ofstream &file)
{
for(unsigned int i = 0; i < strings.size(); i++)
{
file << strings.at(i) << std::endl;
}
}
std::vector <std::string> fileToVector(std::ifstream &file,
std::vector <std::string> strings)
{
std::string line;
if(file.is_open())
{
while(getline(file, line))
{
strings.push_back(line);
}
file.close();
}
return strings;
}
#endif

View File

@ -0,0 +1,11 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris fermentum ac mi quis porta. Aenean vehicula dolor sed leo tristique, ut semper sem sagittis. Nam et faucibus urna. Nam ut neque vitae nisl blandit euismod. Morbi et odio eget ante egestas tempor. Aenean vehicula quis lectus et convallis. Quisque dictum, augue ut ultricies elementum, felis quam rhoncus purus, accumsan tincidunt nunc orci vel nulla. Quisque eros est, tempus nec erat pellentesque, accumsan maximus massa. Aliquam non ante in ex fringilla vehicula in eu magna. Sed aliquet egestas tincidunt. Mauris at libero et nulla mollis bibendum accumsan id metus. Vestibulum ornare nibh ac cursus posuere. Proin efficitur fermentum sapien sit amet porta.
Maecenas luctus neque sed aliquam iaculis. Maecenas turpis metus, fermentum et vehicula eu, consequat ac nisi. Curabitur porta mauris vel nisi vehicula scelerisque. Mauris dolor ex, mattis sit amet porta quis, consectetur volutpat velit. Maecenas sit amet vehicula metus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum ligula mauris, iaculis in porttitor in, viverra quis est. Sed mattis, turpis non facilisis interdum, sem risus volutpat lacus, sed molestie metus tellus vel diam. In nec eleifend ipsum. Donec auctor, dolor fringilla laoreet hendrerit, est tortor luctus nunc, et malesuada lorem diam quis sem. In hac habitasse platea dictumst. Interdum et malesuada fames ac ante ipsum primis in faucibus.
Mauris mollis massa ac massa laoreet posuere. In ullamcorper nunc eu lobortis facilisis. Mauris et risus non ipsum tempus mattis eget a enim. In congue faucibus ante quis iaculis. Vestibulum dictum ultricies augue sed auctor. Maecenas ut mattis leo. Suspendisse quis nisl et elit congue consequat. Fusce tincidunt lacus a tellus lobortis, et tristique diam lacinia.
Aliquam congue enim justo, ac scelerisque risus pharetra quis. Curabitur nec tincidunt nunc, in molestie magna. Maecenas quis tincidunt orci. Ut rutrum ut ex rhoncus accumsan. Nunc sed ligula hendrerit, venenatis urna sit amet, commodo ante. Mauris ac urna viverra, fringilla turpis eu, ornare turpis. Curabitur sodales, sapien sed tristique tristique, dolor leo mollis est, at blandit neque est id mi.
Donec euismod venenatis mauris non bibendum. Duis tellus eros, maximus vel tristique at, congue nec lorem. Vivamus cursus, magna quis lacinia blandit, leo magna varius libero, at pharetra velit metus id massa. Quisque ullamcorper erat eget lacus rhoncus, id imperdiet orci tempus. Sed placerat rutrum vehicula. Suspendisse at sodales dui. Praesent tortor est, ornare vitae cursus sed, hendrerit nec justo. Nam at rhoncus lacus, vitae lobortis nunc. Nunc ac ligula et mauris consequat laoreet. Proin id nulla porttitor, rhoncus massa vel, pretium odio. Aenean in purus velit. Phasellus molestie luctus blandit. Integer nec auctor risus, eu molestie odio. Aliquam ipsum urna, eleifend non ultricies sit amet, blandit sed risus. Pellentesque vitae gravida lacus, vel pellentesque nisi.

View File

@ -0,0 +1,138 @@
#ifndef MAIN_CPP
#define MAIN_CPP
#include <string>
#include <iostream>
#include <vector>
#include "basic.cpp"
struct wordOccurences
{
std::string word;
int occurences;
}
struct previousWords
{
std::string word;
std::vector <wordOccurences> previousWords;
};
struct wordProbabiliy
{
std::string previousWord;
std::string nextWord;
float probability;
}
bool validInput(const std::string userInput)
{
if(stringContainsNumbers(userInput)) return 0;
return 1;
}
std::vector <std::string> divideIntoWords(const std::string userInput)
{
std::vector <std::string> words;
int inputLength = userInput.length();
int wordLength = 0;
for(int i = 0; i < inputLength; i++)
{
if(userInput.at(i) == ' ')
{
words.push_back(userInput.substr(i - wordLength, wordLength));
wordLength = 0;
}else wordLength++;
if(i + 1 == inputLength)
{
words.push_back(userInput.substr(i - wordLength + 1, wordLength + 1));
wordLength = 0;
}
}
return words;
}
int wordRepeats(const std::vector <previousWords> wordsList, const std::string word)
{
int wordsSize = wordsList.size();
for(int i = 0; i < wordsSize; i++)
{
if(wordsList.at(i).word == word) return i;
}
return -1;
}
bool alreadyExists(const std::vector <previousWords> wordsList, const std::string s)
{
for(unsigned int i = 0; i < wordsList.size(); i++)
{
if(s == wordsList.previousWOrds
}
}
std::vector <previousWords> getWordsAndTheirPrevious(const std::vector <std::string> words)
{
std::vector <previousWords> wordsList;
int wordsSize = words.size();
for(int i = 1; i < wordsSize; i++)
{
previousWords temp;
temp.word = words.at(i);
wordOccurences tempTwo;
tempTwo.word = words.at(i - 1));
tempTwo.occurences = 1;
temp.previousWords.push_back(tempTwo);
int position = wordRepeats(wordsList, temp.word);
if(position == -1)
{
wordsList.push_back(temp);
}else
{
wordsList.at(position).previousWords.push_back(temp.previousWords.at(0));
}
}
return wordsList;
}
void printPreviousWord(const previousWords word)
{
std::cout << "The word is \"" << word.word << "\" Words before it are: " << std::endl;
for(unsigned int i = 0; i < word.previousWords.size(); i++)
{
print(word.previousWords.at(i));
}
}
void printPreviousWordsVector(const std::vector <previousWords> v)
{
for(unsigned int i = 0; i < v.size(); i++)
{
printPreviousWord(v.at(i));
}
}
std::vector <wordProbability> getWordProbability(const std::vector <previousWords> wordsList)
{
std::vector <wordProbability> probalityVector;
for(unsigned int i = 0; i - 1 < wordsList.size(); i++)
{
pro
}
}
int main()
{
std::string userInput;
do{
getline(std::cin, userInput);
}while(!validInput(userInput));
std::vector <std::string> words = divideIntoWords(userInput);
std::vector <previousWords> prev = getWordsAndTheirPrevious(words);
printPreviousWordsVector(prev);
return 0;
}
#endif

View File

@ -0,0 +1,27 @@
#include <iostream>
int multiplication(int a, int b)
{
int answer = 0;
for(int i = 0; i < a; i++)
{
answer += b;
}
if(answer != a*b)
{
std::cout << "There is a mistake in your code!" << std::endl;
return -1;
} else return answer;
}
int main()
{
int a,b;
std::cout << "Enter number a" << std::endl;
std::cin >> a;
std::cout << "Enter number b" << std::endl;
std::cin >> b;
std::cout << multiplication(a, b) << std::endl;
return 0;
}

View File

@ -0,0 +1,96 @@
#include <iostream>
#include <vector>
int sumStartEnd(int start, int end)
{
int sum = 0;
for(int i = start; i <= end; i++)
{
sum += i;
}
return sum;
}
int main()
{
std::cout << "Krzysztof" << std::endl;
for(int i = 700; i >= 200; i -= 13) std::cout << i << std::endl;
std::vector <int> array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// if SECOND means 0, 1, TWO
std::cout << array[2] << std::endl;
// if SECOND means 1, TWO
std::cout << array[1] << std::endl;
std::cout << sumStartEnd(0, 1000);
std::string userName;
std::cout << std::endl;
getline(std::cin, userName);
if(userName == "Jack") std::cout << "Hi Jack!" << std::endl;
else std::cout << "Hello, " << userName << std::endl;
for(int i = 0; i <= 100; i++)
{
if(i % 2 == 0) std::cout << i << " is an even number" << std::endl;
else std::cout << i << " is an odd number" << std::endl;
}
bool flag = 1;
for(int i = 0; i <= 100; i++)
{
if(flag) std::cout << i << " is an even number" << std::endl;
else std::cout << i << " is an odd number" << std::endl;
flag = -flag;
}
for(int i = 0; i <= 100; i += 2) std::cout << i << " is an even number " << std::endl;
for(int i = 1; i <= 99; i += 2) std::cout << i << " is an odd number " << std::endl;
for(int i = 1, j = 1; j <= 12; i++)
{
std::cout << i * j << " ";
if(i == 12)
{
i = 1;
j++;
std::cout << std::endl;
}
}
std::cout << std::endl;
std::string sentence;
getline(std::cin, sentence);
std::vector <std::string> words;
std::string temp;
for(unsigned int i = 0; i < sentence.length(); i++)
{
if(sentence.at(i) == ' ' || i + 1 == sentence.length())
{
if(i + 1 == sentence.length()) temp.push_back(sentence.at(i));
words.push_back(temp);
temp = "";
}else temp.push_back(sentence.at(i));
}
for(unsigned int i = 0; i < words.size(); i++)
{
std::cout << words[i] << std::endl;
}
int score;
char project;
std::vector <char> GRADES = {'F', 'C', 'B', 'A'};
std::cin >> score;
std::cout << std::endl;
std::cin >> project;
std::cout << std::endl;
bool doneProject = 0;
if(project == 'Y') doneProject = 1;
if(score < 50) std::cout << GRADES[0 + doneProject];
else if(score < 70) std::cout << GRADES[1 + doneProject];
else if(score < 90) std::cout << GRADES[2 + doneProject];
else std::cout << GRADES[3];
}

View File

@ -0,0 +1,10 @@
#include <random>
#include <iostream>
int main() {
std::random_device rd;
std::uniform_real_distribution<double> dist(1.0, 10.0);
for (int i=0; i<16; ++i)
std::cout << dist(rd) << "\n";
}

View File

@ -0,0 +1,22 @@
#include <iostream>
#include <string>
#include <algorithm>
int main()
{
std::string userString;
getline(std::cin, userString);
int sLength = userString.length();
std::string tempString = userString;
for(int i = 0; i < sLength/2; i++)
{
char temp = tempString[sLength - 1 - i];
tempString[sLength - 1 - i] = tempString[i];
tempString[i] = temp;
}
reverse(userString.begin(), userString.end());
bool correct = tempString == userString;
std::cout << correct << std::endl;
std::cout << tempString << std::endl;
return 0;
}

View File

@ -0,0 +1,48 @@
#include <iostream>
#include <string>
#include <math.h>
const std::string ENTER = "Enter quadratic equation constants: ";
const std::string WHAT_TO_INPUT = "a, b, c as in: ax^2 + bx + c = 0";
const std::string START = ENTER + WHAT_TO_INPUT;
void print(const std::string s) { std::cout << s << std::endl; }
float getDelta(float a, float b, float c)
{
return b*b - 4*a*c;
}
float calculateFirstTerm(float a, float b, float delta)
{
return (-b - sqrt(delta))/(2*a);
}
float calculateSecondTerm(float a, float b, float delta)
{
return (-b + sqrt(delta))/(2*a);
}
int main()
{
print(START);
float a, b, c;
std::cin >> a;
std::cin >> b;
std::cin >> c;
float delta = getDelta(a, b, c);
if(delta < 0)
{
print("delta smaller than 0");
return -1;
}
float x_1 = calculateFirstTerm(a, b, delta);
float x_2 = calculateSecondTerm(a, b, delta);
print("Solutions:");
std::cout << "x_1 = " << x_1 << std::endl;
std::cout << "x_2 = " << x_2 << std::endl;
return 0;
}

View File

@ -0,0 +1,111 @@
#include <iostream>
#include <vector>
void printField(std::vector<unsigned int> &field)
{
std::cout << std::endl;
for(int i = 0; i < 9; i++)
{
if(i % 3 == 0) std::cout << std::endl;
if(field[i] == 0) std::cout << "-";
else if(field[i] == 1) std::cout << "X";
else if(field[i] == 2) std::cout << "O";
}
std::cout << std::endl;
}
unsigned int chooseField(unsigned int playerNumber, std::vector<unsigned int> &field)
{
unsigned int chosenField;
do
{
std::cout << "player " << playerNumber << " choose a field:" << std::endl;
std::cin >> chosenField;
}while(field[chosenField] != 0);
return chosenField;
}
bool vertical(unsigned int playerNumber, std::vector<unsigned int> &field)
{
if((field[0] == playerNumber && field[1] == playerNumber && field[2] == playerNumber)
|| (field[3] == playerNumber && field[4] == playerNumber && field[5] == playerNumber)
|| (field[6] == playerNumber && field[7] == playerNumber && field[8] == playerNumber))
{
return 1;
}else return 0;
}
bool horizontal(unsigned int playerNumber, std::vector<unsigned int> &field)
{
if((field[0] == playerNumber && field[3] == playerNumber && field[6] == playerNumber)
|| (field[1] == playerNumber && field[4] == playerNumber && field[7] == playerNumber)
|| (field[2] == playerNumber && field[5] == playerNumber && field[8] == playerNumber))
{
return 1;
}else return 0;
}
bool across(unsigned int playerNumber, std::vector<unsigned int> &field)
{
if((field[0] == playerNumber && field[4] == playerNumber && field[8] == playerNumber)
|| (field[2] == playerNumber && field[4] == playerNumber && field[6] == playerNumber))
{
return 1;
}else return 0;
}
bool checkPlayerWin(unsigned int playerNumber, std::vector<unsigned int> &field)
{
if(vertical(playerNumber, field)) return 1;
if(horizontal(playerNumber, field)) return 1;
if(across(playerNumber, field)) return 1;
else return 0;
}
unsigned int checkIfWin(std::vector<unsigned int> &field)
{
if(checkPlayerWin(1, field)) return 1;
else if(checkPlayerWin(2, field)) return 2;
else return 0;
}
bool checkIfFilled(std::vector<unsigned int> &field)
{
bool filled = 1;
for(int i = 0; i < 9; i++)
{
if(field[i] == 0)
{
filled = 0;
return filled;
}
}
return filled;
}
bool turn(unsigned int playerNumber, std::vector<unsigned int> &field, bool *filled, unsigned int *whoWon)
{
field[chooseField(playerNumber, field)] = playerNumber;
printField(field);
*whoWon = checkIfWin(field);
*filled = checkIfFilled(field);
if(*whoWon != 0 || *filled != 0) return 1;
else return 0;
}
int main()
{
std::vector<unsigned int> field = {0, 0, 0, 0, 0, 0, 0, 0, 0};
unsigned int whoWon = 0;
bool filled = 0;
while(whoWon == 0 || filled == 0)
{
if(turn(1, field, &filled, &whoWon)) break;
if(turn(2, field, &filled, &whoWon)) break;
}
if(!filled) std::cout << "Player " << whoWon << " Won!" << std::endl;
else std::cout << "DRAW!" << std::endl;
return 0;
}

View File

@ -0,0 +1,89 @@
#include <iostream>
#include <string>
#include <vector>
const std::vector <std::string> TIERS = {"Abhorrent", "Bad", "Mid", "Good", "Top", "God Tier"};
const float TIER_BASE = TIERS.size();
void print(std::string const s)
{
std::cout << s << std::endl;
}
bool errorUserInput(std::string userInput)
{
if(userInput.find("/") == std::string::npos)
{
print("No '/' was found!");
return 1;
}
size_t positionOfSlash = userInput.find("/");
std::string nominatorS = userInput.substr(0, positionOfSlash);
try
{
float nominator = stof(nominatorS);
}catch ( std::invalid_argument )
{
print("No number was found before the slash!");
return 1;
}
std::string denominatorS = userInput.substr(positionOfSlash + 1, userInput.length() - 1);
try
{
float denominator = stof(denominatorS);
if(denominator == 0)
{
print("You cannot divide by 0!");
return 1;
}
}catch ( std::invalid_argument )
{
print("No number was found after the slash!");
return 1;
}
return 0;
}
std::string convertToTier(float nominator, float denominator)
{
float fraction = nominator / denominator;
int tierIndex;
for(int i = TIER_BASE; i > 0; i--)
{
if(fraction >= ( i / TIER_BASE))
{
tierIndex = i - 1;
break;
}
}
if(tierIndex == 0 & fraction > (1.1/10.0)) return TIERS[1];
return TIERS[tierIndex];
}
int main()
{
std::string userScore;
do
{
print("Enter your score in a format: numberOne/numberTwo");
getline(std::cin, userScore);
}while(errorUserInput(userScore));
size_t positionOfSlash = userScore.find("/");
std::string nominatorS = userScore.substr(0, positionOfSlash);
float nominator = stof(nominatorS);
std::string denominatorS = userScore.substr(positionOfSlash + 1, userScore.length() - 1);
float denominator = stof(denominatorS);
print(convertToTier(nominator, denominator));
return 0;
}

View File

@ -0,0 +1,11 @@
#include <stdio.h>
int main()
{
int x = 10;
while (x-- > 0)
{
printf("%d;", x);
}
return 0;
}

View File

@ -0,0 +1,22 @@
// bernoulli_distribution
#include <iostream>
#include <random>
int main()
{
const int nrolls=10000;
std::random_device rd;
std::mt19937 gen(rd());
std::bernoulli_distribution distribution(0.5);
int count=0; // count number of trues
for (int i=0; i<nrolls; ++i) if (distribution(gen)) ++count;
std::cout << "bernoulli_distribution (0.5) x 10000:" << std::endl;
std::cout << "true: " << count << std::endl;
std::cout << "false: " << nrolls-count << std::endl;
return 0;
}

View File

@ -0,0 +1,13 @@
#include <random>
#include <iostream>
int main() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0, 1);
for(int i = 0; i < 10; i++) {
std::cout << dis(gen) << std::endl;
}return 0;
}

View File

@ -0,0 +1,2 @@
yousuckatcards:
g++ -Wall -Wextra -pedantic yousuckatcards.cpp

View File

@ -0,0 +1,127 @@
#include <iostream>
#include <random>
#include <string>
const int SEQUENCE_LENGTH = 3;
const bool BOT_WON = 0;
const bool PLAYER_WON = 1;
const int NOBODY_WON = 2;
void print(std::string const s)
{
std::cout << s << std::endl;
}
bool validSequence(std::string const s)
{
if(s.size() != SEQUENCE_LENGTH)
{
print("Sequence too long");
return false;
}
if( (s[0] != 'B' && s[0] != 'R') ||
(s[1] != 'B' && s[1] != 'R') ||
(s[2] != 'B' && s[2] != 'R'))
{
print("Sequence consists of illegal signs!");
return false;
}
return true;
}
std::string playerChoice()
{
std::string playerSequence;
do
{
std::cin >> playerSequence;
}
while(!validSequence(playerSequence));
return playerSequence;
}
std::string botChoice(std::string const playerSequence)
{
std::string botSequence;
if(playerSequence[1] == 'B') botSequence.push_back('R');
else botSequence.push_back('B');
botSequence.push_back(playerSequence[0]);
botSequence.push_back(playerSequence[2]);
return botSequence;
}
int compareGeneratedAndPlayers(std::string playerSequence, std::string botSequence, std::string generatedSequence)
{
int generatedSequenceLength = generatedSequence.length();
std::string sequenceToCompare = generatedSequence.substr(generatedSequenceLength - SEQUENCE_LENGTH, generatedSequenceLength);
if(sequenceToCompare.compare(playerSequence) == 0) return PLAYER_WON;
if(sequenceToCompare.compare(botSequence) == 0) return BOT_WON;
else return NOBODY_WON;
}
bool game(std::string playerSequence, std::string botSequence)
{
std::string generatedSequence;
std::random_device rd;
std::mt19937 gen(rd());
std::bernoulli_distribution distribution(0.5);
for(int i = 0; i < SEQUENCE_LENGTH; i++)
{
if(distribution(gen)) generatedSequence.push_back('R');
else generatedSequence.push_back('B');
}
while(compareGeneratedAndPlayers(playerSequence, botSequence, generatedSequence) == NOBODY_WON)
{
if(distribution(gen)) generatedSequence.push_back('R');
else generatedSequence.push_back('B');
}
print(generatedSequence);
if(compareGeneratedAndPlayers(playerSequence, botSequence, generatedSequence) == PLAYER_WON) return PLAYER_WON;
else return BOT_WON;
}
void score(int playerWins, int botWins)
{
std::cout << "Player won: " << playerWins << " times!" << std::endl;
std::cout << "Bot won: " << botWins << " times!" << std::endl;
}
int main()
{
int playerWins = 0;
int botWins = 0;
do
{
print("Do you want to play the game? 1 - yes, 0 - no");
bool continue_ = 1;
std::string playerInput;
std::cin >> playerInput;
if(playerInput[0] == '1') continue_ = 1;
else continue_ = 0;
if(!continue_) break;
std::string playerSequence;
print("Write three colors sequence created from 52 cards from the deck (26 Black, 26 Red), write B for Black and R for Red");
playerSequence = playerChoice();
std::string botSequence = botChoice(playerSequence);
print("Bot has chosen this sequence:");
print(botSequence);
if(game(playerSequence, botSequence))
{
print("You won!");
playerWins++;
score(playerWins, botWins);
}
else
{
print("Bot won!");
botWins++;
score(playerWins, botWins);
}
}while(1);
return 1;
}

View File

@ -0,0 +1,8 @@
#include <iostream>
int main()
{
float X = 1/2;
std::cout << X << std::endl;
return 0;
}

2
LaTeX/.gitignore vendored
View File

@ -305,4 +305,4 @@ TSWLatexianTemp*
# option is specified. Footnotes are the stored in a file with suffix Notes.bib. # option is specified. Footnotes are the stored in a file with suffix Notes.bib.
# Uncomment the next line to have this generated file ignored. # Uncomment the next line to have this generated file ignored.
#*Notes.bib #*Notes.bib
*.pdf *.pdf

View File

@ -41,4 +41,4 @@ Na czym polega odmaskowanie dwuuszne (BMLD)? \\
Podać typowe wartości odmaskowania dwuusznego. \\ Podać typowe wartości odmaskowania dwuusznego. \\
Typowe wartości odmaskowania dwuusznego (MLD) to różnice w progu słyszenia sygnału w obecności szumu przy korzystaniu ze słuchu obuusznego. MLD jest największe dla niskich częstotliwości, osiągając około **15 dB** dla 500 Hz. Dla średnich częstotliwości, takich jak 1000 Hz, MLD wynosi około **10 dB**. W przypadku wyższych częstotliwości, powyżej 1500 Hz, MLD maleje do wartości często poniżej **5 dB**. Te wartości pokazują, że słuch obuuszny znacząco poprawia wykrywanie sygnałów w szumie na niskich i średnich częstotliwościach. Typowe wartości odmaskowania dwuusznego (MLD) to różnice w progu słyszenia sygnału w obecności szumu przy korzystaniu ze słuchu obuusznego. MLD jest największe dla niskich częstotliwości, osiągając około **15 dB** dla 500 Hz. Dla średnich częstotliwości, takich jak 1000 Hz, MLD wynosi około **10 dB**. W przypadku wyższych częstotliwości, powyżej 1500 Hz, MLD maleje do wartości często poniżej **5 dB**. Te wartości pokazują, że słuch obuuszny znacząco poprawia wykrywanie sygnałów w szumie na niskich i średnich częstotliwościach.
\end{document} \end{document}

View File

@ -12,10 +12,10 @@
\SetBgContents{\tikz{\draw[step=\mylen] (-.5\paperwidth,-.5\paperheight) grid (.5\paperwidth,.5\paperheight);}} \SetBgContents{\tikz{\draw[step=\mylen] (-.5\paperwidth,-.5\paperheight) grid (.5\paperwidth,.5\paperheight);}}
\begin{document} \begin{document}
\newpage \newpage
\ % The empty page \ % The empty page
\newpage \newpage
\end{document} \end{document}

2
PYTHON/.gitignore vendored
View File

@ -215,4 +215,4 @@ __marimo__/
# Streamlit # Streamlit
.streamlit/secrets.toml .streamlit/secrets.toml
*lichess_db_puzzle.csv* *lichess_db_puzzle.csv*

View File

@ -0,0 +1,34 @@
import json
import os
from pathlib import Path
import requests
requests_send = 0
while requests_send < 90:
res = requests.get("https://api.thecatapi.com/v1/images/search?limit=100&api_key=")
requests_send += 1
response = json.loads(res.text)
urls = []
for cat in response:
urls.append(cat.get("url"))
Path("./CATS2").mkdir(parents=True, exist_ok=True)
for url in urls:
try:
# Get the image content
response = requests.get(url)
response.raise_for_status() # Raise an exception for HTTP errors
# Extract the image name from the URL
image_name = os.path.basename(url)
image_path = os.path.join("./CATS2/", image_name)
# Save the image to the directory
with open(image_path, "wb") as file:
file.write(response.content)
print(f"Saved {url} as {image_path}")
except requests.exceptions.RequestException as e:
print(f"Failed to download {url}: {e}")

View File

@ -0,0 +1,4 @@
json
os
pathlib
requests

111
PYTHON/extractLinks/main.py Normal file → Executable file
View File

@ -1,6 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """Extract hosts from href attributes in an HTML file and write them as *host* per line.
Extract hosts from href attributes in an HTML file and write them as *host* per line.
Usage: Usage:
python main.py INPUT_HTML [OUTPUT_TXT] python main.py INPUT_HTML [OUTPUT_TXT]
@ -12,79 +11,79 @@ alongside the input file.
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import os
from html.parser import HTMLParser from html.parser import HTMLParser
from typing import List, Set import os
from urllib.parse import urlparse from urllib.parse import urlparse
class _HrefParser(HTMLParser): class _HrefParser(HTMLParser):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self.hrefs: List[str] = [] self.hrefs: list[str] = []
def handle_starttag(self, tag: str, attrs): # type: ignore[override] def handle_starttag(self, tag: str, attrs): # type: ignore[override]
# Collect any href attribute on any tag # Collect any href attribute on any tag
for (k, v) in attrs: for k, v in attrs:
if k.lower() == "href" and v is not None: if k.lower() == "href" and v is not None:
self.hrefs.append(v) self.hrefs.append(v)
def extract_hosts_from_html(html_text: str) -> List[str]: def extract_hosts_from_html(html_text: str) -> list[str]:
"""Parse HTML text, extract href values, and return a list of hostnames. """Parse HTML text, extract href values, and return a list of hostnames.
Rules: Rules:
- Only http/https URLs are considered. - Only http/https URLs are considered.
- Output is the network location (host[:port]) without scheme or path. - Output is the network location (host[:port]) without scheme or path.
- Duplicates are removed, preserving first-seen order. - Duplicates are removed, preserving first-seen order.
""" """
parser = _HrefParser() parser = _HrefParser()
parser.feed(html_text) parser.feed(html_text)
seen: Set[str] = set() seen: set[str] = set()
hosts: List[str] = [] hosts: list[str] = []
for href in parser.hrefs: for href in parser.hrefs:
parsed = urlparse(href) parsed = urlparse(href)
if parsed.scheme in {"http", "https"} and parsed.netloc: if parsed.scheme in {"http", "https"} and parsed.netloc:
host = parsed.netloc host = parsed.netloc
if host not in seen: if host not in seen:
seen.add(host) seen.add(host)
hosts.append(host) hosts.append(host)
return hosts return hosts
def main() -> int: def main() -> int:
ap = argparse.ArgumentParser(description="Extract hosts from hrefs in an HTML file.") ap = argparse.ArgumentParser(
ap.add_argument("input_html", help="Path to input HTML file") description="Extract hosts from hrefs in an HTML file."
ap.add_argument( )
"output_txt", ap.add_argument("input_html", help="Path to input HTML file")
nargs="?", ap.add_argument(
help="Path to output text file (defaults to <input_basename>_links.txt in the same directory)", "output_txt",
) nargs="?",
args = ap.parse_args() help="Path to output text file (defaults to <input_basename>_links.txt in the same directory)",
)
args = ap.parse_args()
input_path = args.input_html input_path = args.input_html
if not os.path.isfile(input_path): if not os.path.isfile(input_path):
raise SystemExit(f"Input file not found: {input_path}") raise SystemExit(f"Input file not found: {input_path}")
out_path = args.output_txt out_path = args.output_txt
if not out_path: if not out_path:
base = os.path.splitext(os.path.basename(input_path))[0] base = os.path.splitext(os.path.basename(input_path))[0]
out_path = os.path.join(os.path.dirname(input_path), f"{base}_links.txt") out_path = os.path.join(os.path.dirname(input_path), f"{base}_links.txt")
with open(input_path, "r", encoding="utf-8", errors="ignore") as f: with open(input_path, encoding="utf-8", errors="ignore") as f:
html_text = f.read() html_text = f.read()
hosts = extract_hosts_from_html(html_text) hosts = extract_hosts_from_html(html_text)
with open(out_path, "w", encoding="utf-8") as f: with open(out_path, "w", encoding="utf-8") as f:
for host in hosts: for host in hosts:
f.write(f"*{host}*\n") f.write(f"*{host}*\n")
print(f"Wrote {len(hosts)} host(s) to {out_path}") print(f"Wrote {len(hosts)} host(s) to {out_path}")
return 0 return 0
if __name__ == "__main__": if __name__ == "__main__":
raise SystemExit(main()) raise SystemExit(main())

View File

@ -1,9 +1,6 @@
import os from pathlib import Path
import subprocess import subprocess
import sys import sys
from pathlib import Path
import pytest
# Allow importing from project root when running pytest from this folder # Allow importing from project root when running pytest from this folder
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]

View File

@ -10,7 +10,7 @@ A fun 2-player cooperative word game where players take turns selecting adjacent
4. **Word Formation**: Continue taking turns until you want to submit a word 4. **Word Formation**: Continue taking turns until you want to submit a word
5. **Scoring**: Press ENTER to submit the word. Valid words score points exponentially based on length: 5. **Scoring**: Press ENTER to submit the word. Valid words score points exponentially based on length:
- 3 letters: 2 points - 3 letters: 2 points
- 4 letters: 4 points - 4 letters: 4 points
- 5 letters: 8 points - 5 letters: 8 points
- 6 letters: 16 points - 6 letters: 16 points
- And so on... - And so on...
@ -25,6 +25,7 @@ A fun 2-player cooperative word game where players take turns selecting adjacent
## Keyboard Adjacency ## Keyboard Adjacency
Each key is adjacent to its neighbors (including diagonals). For example: Each key is adjacent to its neighbors (including diagonals). For example:
- 'S' is adjacent to: Q, W, E, A, D, Z, X, C - 'S' is adjacent to: Q, W, E, A, D, Z, X, C
- 'F' is adjacent to: E, R, T, D, G, C, V, B - 'F' is adjacent to: E, R, T, D, G, C, V, B

View File

@ -1,8 +1,9 @@
import pygame
import sys
import json import json
import os import os
import random import random
import sys
import pygame
# Initialize Pygame # Initialize Pygame
pygame.init() pygame.init()
@ -21,41 +22,42 @@ PLAYER_COLORS = [(255, 100, 100), (100, 100, 255)]
# Keyboard layout # Keyboard layout
KEYBOARD_LAYOUT = [ KEYBOARD_LAYOUT = [
['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'], ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'], ["a", "s", "d", "f", "g", "h", "j", "k", "l"],
['z', 'x', 'c', 'v', 'b', 'n', 'm'] ["z", "x", "c", "v", "b", "n", "m"],
] ]
# Key adjacency mapping # Key adjacency mapping
KEY_ADJACENCY = { KEY_ADJACENCY = {
'q': ['w', 'a', 's'], "q": ["w", "a", "s"],
'w': ['q', 'e', 'a', 's', 'd'], "w": ["q", "e", "a", "s", "d"],
'e': ['w', 'r', 's', 'd', 'f'], "e": ["w", "r", "s", "d", "f"],
'r': ['e', 't', 'd', 'f', 'g'], "r": ["e", "t", "d", "f", "g"],
't': ['r', 'y', 'f', 'g', 'h'], "t": ["r", "y", "f", "g", "h"],
'y': ['t', 'u', 'g', 'h', 'j'], "y": ["t", "u", "g", "h", "j"],
'u': ['y', 'i', 'h', 'j', 'k'], "u": ["y", "i", "h", "j", "k"],
'i': ['u', 'o', 'j', 'k', 'l'], "i": ["u", "o", "j", "k", "l"],
'o': ['i', 'p', 'k', 'l'], "o": ["i", "p", "k", "l"],
'p': ['o', 'l'], "p": ["o", "l"],
'a': ['q', 'w', 's', 'z', 'x'], "a": ["q", "w", "s", "z", "x"],
's': ['q', 'w', 'e', 'a', 'd', 'z', 'x', 'c'], "s": ["q", "w", "e", "a", "d", "z", "x", "c"],
'd': ['w', 'e', 'r', 's', 'f', 'x', 'c', 'v'], "d": ["w", "e", "r", "s", "f", "x", "c", "v"],
'f': ['e', 'r', 't', 'd', 'g', 'c', 'v', 'b'], "f": ["e", "r", "t", "d", "g", "c", "v", "b"],
'g': ['r', 't', 'y', 'f', 'h', 'v', 'b', 'n'], "g": ["r", "t", "y", "f", "h", "v", "b", "n"],
'h': ['t', 'y', 'u', 'g', 'j', 'b', 'n', 'm'], "h": ["t", "y", "u", "g", "j", "b", "n", "m"],
'j': ['y', 'u', 'i', 'h', 'k', 'n', 'm'], "j": ["y", "u", "i", "h", "k", "n", "m"],
'k': ['u', 'i', 'o', 'j', 'l', 'm'], "k": ["u", "i", "o", "j", "l", "m"],
'l': ['i', 'o', 'p', 'k'], "l": ["i", "o", "p", "k"],
'z': ['a', 's', 'x'], "z": ["a", "s", "x"],
'x': ['a', 's', 'd', 'z', 'c'], "x": ["a", "s", "d", "z", "c"],
'c': ['s', 'd', 'f', 'x', 'v'], "c": ["s", "d", "f", "x", "v"],
'v': ['d', 'f', 'g', 'c', 'b'], "v": ["d", "f", "g", "c", "b"],
'b': ['f', 'g', 'h', 'v', 'n'], "b": ["f", "g", "h", "v", "n"],
'n': ['g', 'h', 'j', 'b', 'm'], "n": ["g", "h", "j", "b", "m"],
'm': ['h', 'j', 'k', 'n'] "m": ["h", "j", "k", "n"],
} }
class KeyboardCoopGame: class KeyboardCoopGame:
def __init__(self): def __init__(self):
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
@ -64,10 +66,10 @@ class KeyboardCoopGame:
self.font = pygame.font.Font(None, 24) self.font = pygame.font.Font(None, 24)
self.large_font = pygame.font.Font(None, 32) self.large_font = pygame.font.Font(None, 32)
self.small_font = pygame.font.Font(None, 20) self.small_font = pygame.font.Font(None, 20)
# Load dictionary # Load dictionary
self.dictionary = self.load_dictionary() self.dictionary = self.load_dictionary()
# Initialize game state # Initialize game state
self.current_player = 0 self.current_player = 0
self.current_word = "" self.current_word = ""
@ -75,18 +77,20 @@ class KeyboardCoopGame:
self.score = 0 self.score = 0
self.game_over = False self.game_over = False
self.message = "Player 1: Choose any letter to start!" self.message = "Player 1: Choose any letter to start!"
# Generate random keyboard layout and adjacency # Generate random keyboard layout and adjacency
self.generate_random_keyboard() self.generate_random_keyboard()
# Key positions # Key positions
self.key_positions = self.calculate_key_positions() self.key_positions = self.calculate_key_positions()
def load_dictionary(self): def load_dictionary(self):
"""Load dictionary from words_dictionary.json file""" """Load dictionary from words_dictionary.json file"""
try: try:
dictionary_path = os.path.join(os.path.dirname(__file__), 'words_dictionary.json') dictionary_path = os.path.join(
with open(dictionary_path, 'r', encoding='utf-8') as f: os.path.dirname(__file__), "words_dictionary.json"
)
with open(dictionary_path, encoding="utf-8") as f:
dictionary_data = json.load(f) dictionary_data = json.load(f)
# Convert to set for faster lookup (we only need the keys) # Convert to set for faster lookup (we only need the keys)
return set(dictionary_data.keys()) return set(dictionary_data.keys())
@ -94,65 +98,142 @@ class KeyboardCoopGame:
print("Warning: words_dictionary.json not found, using fallback dictionary") print("Warning: words_dictionary.json not found, using fallback dictionary")
# Fallback to a smaller dictionary if file not found # Fallback to a smaller dictionary if file not found
return { return {
'cat', 'dog', 'car', 'bat', 'rat', 'hat', 'mat', 'sat', 'fat', 'pat', "cat",
'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'had', "dog",
'her', 'was', 'one', 'our', 'out', 'day', 'get', 'has', 'him', 'his', "car",
'how', 'man', 'new', 'now', 'old', 'see', 'two', 'way', 'who', 'boy', "bat",
'work', 'know', 'place', 'year', 'live', 'me', 'back', 'give', 'good' "rat",
"hat",
"mat",
"sat",
"fat",
"pat",
"the",
"and",
"for",
"are",
"but",
"not",
"you",
"all",
"can",
"had",
"her",
"was",
"one",
"our",
"out",
"day",
"get",
"has",
"him",
"his",
"how",
"man",
"new",
"now",
"old",
"see",
"two",
"way",
"who",
"boy",
"work",
"know",
"place",
"year",
"live",
"me",
"back",
"give",
"good",
} }
except json.JSONDecodeError: except json.JSONDecodeError:
print("Warning: Error reading words_dictionary.json, using fallback dictionary") print(
"Warning: Error reading words_dictionary.json, using fallback dictionary"
)
return { return {
'cat', 'dog', 'car', 'bat', 'rat', 'hat', 'mat', 'sat', 'fat', 'pat', "cat",
'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'had', "dog",
'work', 'know', 'place', 'year', 'live', 'me', 'back', 'give', 'good' "car",
"bat",
"rat",
"hat",
"mat",
"sat",
"fat",
"pat",
"the",
"and",
"for",
"are",
"but",
"not",
"you",
"all",
"can",
"had",
"work",
"know",
"place",
"year",
"live",
"me",
"back",
"give",
"good",
} }
def generate_random_keyboard(self): def generate_random_keyboard(self):
"""Generate a random keyboard layout and calculate adjacencies""" """Generate a random keyboard layout and calculate adjacencies"""
# All 26 letters # All 26 letters
all_letters = list('abcdefghijklmnopqrstuvwxyz') all_letters = list("abcdefghijklmnopqrstuvwxyz")
random.shuffle(all_letters) random.shuffle(all_letters)
# Create random layout with same structure as QWERTY (10-9-7) # Create random layout with same structure as QWERTY (10-9-7)
self.keyboard_layout = [ self.keyboard_layout = [
all_letters[0:10], # Top row: 10 keys all_letters[0:10], # Top row: 10 keys
all_letters[10:19], # Middle row: 9 keys all_letters[10:19], # Middle row: 9 keys
all_letters[19:26] # Bottom row: 7 keys all_letters[19:26], # Bottom row: 7 keys
] ]
# Update available letters # Update available letters
self.available_letters = set(all_letters) self.available_letters = set(all_letters)
# Calculate adjacencies based on new layout # Calculate adjacencies based on new layout
self.calculate_adjacencies() self.calculate_adjacencies()
def calculate_adjacencies(self): def calculate_adjacencies(self):
"""Calculate adjacencies based on current keyboard layout""" """Calculate adjacencies based on current keyboard layout"""
self.key_adjacency = {} self.key_adjacency = {}
for row_idx, row in enumerate(self.keyboard_layout): for row_idx, row in enumerate(self.keyboard_layout):
for col_idx, letter in enumerate(row): for col_idx, letter in enumerate(row):
adjacents = [] adjacents = []
# Check all 8 directions (including diagonals) # Check all 8 directions (including diagonals)
directions = [ directions = [
(-1, -1), (-1, 0), (-1, 1), # Above (-1, -1),
(0, -1), (0, 1), # Same row (-1, 0),
(1, -1), (1, 0), (1, 1) # Below (-1, 1), # Above
(0, -1),
(0, 1), # Same row
(1, -1),
(1, 0),
(1, 1), # Below
] ]
for dr, dc in directions: for dr, dc in directions:
new_row = row_idx + dr new_row = row_idx + dr
new_col = col_idx + dc new_col = col_idx + dc
# Check bounds # Check bounds
if 0 <= new_row < len(self.keyboard_layout): if 0 <= new_row < len(self.keyboard_layout):
if 0 <= new_col < len(self.keyboard_layout[new_row]): if 0 <= new_col < len(self.keyboard_layout[new_row]):
adjacents.append(self.keyboard_layout[new_row][new_col]) adjacents.append(self.keyboard_layout[new_row][new_col])
self.key_adjacency[letter] = adjacents self.key_adjacency[letter] = adjacents
def calculate_key_positions(self): def calculate_key_positions(self):
"""Calculate the position of each key on screen""" """Calculate the position of each key on screen"""
positions = {} positions = {}
@ -161,81 +242,87 @@ class KeyboardCoopGame:
key_spacing = 8 key_spacing = 8
start_x = 50 start_x = 50
start_y = 320 start_y = 320
for row_idx, row in enumerate(self.keyboard_layout): for row_idx, row in enumerate(self.keyboard_layout):
row_offset = row_idx * 30 # Offset for layout row_offset = row_idx * 30 # Offset for layout
for col_idx, key in enumerate(row): for col_idx, key in enumerate(row):
x = start_x + col_idx * (key_width + key_spacing) + row_offset x = start_x + col_idx * (key_width + key_spacing) + row_offset
y = start_y + row_idx * (key_height + key_spacing) y = start_y + row_idx * (key_height + key_spacing)
positions[key] = pygame.Rect(x, y, key_width, key_height) positions[key] = pygame.Rect(x, y, key_width, key_height)
return positions return positions
def get_key_at_position(self, pos): def get_key_at_position(self, pos):
"""Get the key at the given mouse position""" """Get the key at the given mouse position"""
for key, rect in self.key_positions.items(): for key, rect in self.key_positions.items():
if rect.collidepoint(pos): if rect.collidepoint(pos):
return key return key
return None return None
def is_valid_move(self, letter): def is_valid_move(self, letter):
"""Check if the letter is a valid move""" """Check if the letter is a valid move"""
if not self.selected_letters: if not self.selected_letters:
return True # First move can be any letter return True # First move can be any letter
last_letter = self.selected_letters[-1] last_letter = self.selected_letters[-1]
return letter in self.key_adjacency[last_letter] return letter in self.key_adjacency[last_letter]
def is_valid_word(self, word): def is_valid_word(self, word):
"""Check if the word is in the dictionary""" """Check if the word is in the dictionary"""
return word.lower() in self.dictionary return word.lower() in self.dictionary
def calculate_score(self, word_length): def calculate_score(self, word_length):
"""Calculate score exponentially based on word length""" """Calculate score exponentially based on word length"""
if word_length < 3: if word_length < 3:
return 0 return 0
return 2 ** (word_length - 2) return 2 ** (word_length - 2)
def handle_letter_click(self, letter): def handle_letter_click(self, letter):
"""Handle clicking on a letter""" """Handle clicking on a letter"""
if letter in self.available_letters and self.is_valid_move(letter): if letter in self.available_letters and self.is_valid_move(letter):
self.selected_letters.append(letter) self.selected_letters.append(letter)
self.current_word += letter self.current_word += letter
# Update available letters to include adjacent letters AND the same letter # Update available letters to include adjacent letters AND the same letter
adjacent_letters = set(self.key_adjacency[letter]) if letter in self.key_adjacency else set() adjacent_letters = (
set(self.key_adjacency[letter])
if letter in self.key_adjacency
else set()
)
adjacent_letters.add(letter) # Allow selecting the same letter again adjacent_letters.add(letter) # Allow selecting the same letter again
self.available_letters = adjacent_letters self.available_letters = adjacent_letters
# Switch player # Switch player
self.current_player = 1 - self.current_player self.current_player = 1 - self.current_player
self.message = f"Player {self.current_player + 1}: Choose an adjacent letter!" self.message = (
f"Player {self.current_player + 1}: Choose an adjacent letter!"
)
# If no valid moves available, force word submission # If no valid moves available, force word submission
if not self.available_letters: if not self.available_letters:
self.submit_word() self.submit_word()
def submit_word(self): def submit_word(self):
"""Submit the current word and check if it's valid""" """Submit the current word and check if it's valid"""
if len(self.current_word) >= 3 and self.is_valid_word(self.current_word): if len(self.current_word) >= 3 and self.is_valid_word(self.current_word):
points = self.calculate_score(len(self.current_word)) points = self.calculate_score(len(self.current_word))
self.score += points self.score += points
self.message = f"'{self.current_word}' is valid! +{points} points (Total: {self.score}) - New keyboard!" self.message = f"'{self.current_word}' is valid! +{points} points (Total: {self.score}) - New keyboard!"
# Randomize keyboard layout after scoring # Randomize keyboard layout after scoring
self.generate_random_keyboard() self.generate_random_keyboard()
self.key_positions = self.calculate_key_positions() self.key_positions = self.calculate_key_positions()
elif len(self.current_word) < 3: elif len(self.current_word) < 3:
self.message = f"'{self.current_word}' is too short! (minimum 3 letters)" self.message = f"'{self.current_word}' is too short! (minimum 3 letters)"
else: else:
self.message = f"'{self.current_word}' is not a valid word!" self.message = f"'{self.current_word}' is not a valid word!"
# Reset for next word # Reset for next word
self.current_word = "" self.current_word = ""
self.selected_letters = [] self.selected_letters = []
self.current_player = 0 self.current_player = 0
def reset_game(self): def reset_game(self):
"""Reset the game to initial state""" """Reset the game to initial state"""
self.current_player = 0 self.current_player = 0
@ -244,15 +331,15 @@ class KeyboardCoopGame:
self.score = 0 self.score = 0
self.game_over = False self.game_over = False
self.message = "Player 1: Choose any letter to start!" self.message = "Player 1: Choose any letter to start!"
# Generate new random keyboard layout # Generate new random keyboard layout
self.generate_random_keyboard() self.generate_random_keyboard()
self.key_positions = self.calculate_key_positions() self.key_positions = self.calculate_key_positions()
def draw_keyboard(self): def draw_keyboard(self):
"""Draw the virtual keyboard""" """Draw the virtual keyboard"""
mouse_pos = pygame.mouse.get_pos() mouse_pos = pygame.mouse.get_pos()
for letter, rect in self.key_positions.items(): for letter, rect in self.key_positions.items():
# Determine key color # Determine key color
if letter in self.selected_letters: if letter in self.selected_letters:
@ -263,39 +350,43 @@ class KeyboardCoopGame:
color = KEY_HOVER_COLOR color = KEY_HOVER_COLOR
else: else:
color = KEY_COLOR color = KEY_COLOR
# Draw key # Draw key
pygame.draw.rect(self.screen, color, rect) pygame.draw.rect(self.screen, color, rect)
pygame.draw.rect(self.screen, TEXT_COLOR, rect, 2) pygame.draw.rect(self.screen, TEXT_COLOR, rect, 2)
# Draw letter # Draw letter
text = self.small_font.render(letter.upper(), True, TEXT_COLOR) text = self.small_font.render(letter.upper(), True, TEXT_COLOR)
text_rect = text.get_rect(center=rect.center) text_rect = text.get_rect(center=rect.center)
self.screen.blit(text, text_rect) self.screen.blit(text, text_rect)
def draw_ui(self): def draw_ui(self):
"""Draw the user interface""" """Draw the user interface"""
# Title # Title
title = self.large_font.render("Keyboard Coop Game", True, TEXT_COLOR) title = self.large_font.render("Keyboard Coop Game", True, TEXT_COLOR)
self.screen.blit(title, (30, 20)) self.screen.blit(title, (30, 20))
# Current word # Current word
word_text = self.font.render(f"Current Word: {self.current_word.upper()}", True, TEXT_COLOR) word_text = self.font.render(
f"Current Word: {self.current_word.upper()}", True, TEXT_COLOR
)
self.screen.blit(word_text, (30, 50)) self.screen.blit(word_text, (30, 50))
# Score # Score
score_text = self.font.render(f"Score: {self.score}", True, TEXT_COLOR) score_text = self.font.render(f"Score: {self.score}", True, TEXT_COLOR)
self.screen.blit(score_text, (30, 75)) self.screen.blit(score_text, (30, 75))
# Current player # Current player
player_color = PLAYER_COLORS[self.current_player] player_color = PLAYER_COLORS[self.current_player]
player_text = self.font.render(f"Current Player: {self.current_player + 1}", True, player_color) player_text = self.font.render(
f"Current Player: {self.current_player + 1}", True, player_color
)
self.screen.blit(player_text, (30, 100)) self.screen.blit(player_text, (30, 100))
# Message # Message
message_text = self.font.render(self.message, True, TEXT_COLOR) message_text = self.font.render(self.message, True, TEXT_COLOR)
self.screen.blit(message_text, (30, 125)) self.screen.blit(message_text, (30, 125))
# Instructions - Move to right side and make more compact # Instructions - Move to right side and make more compact
instructions = [ instructions = [
"Instructions:", "Instructions:",
@ -305,14 +396,14 @@ class KeyboardCoopGame:
"• Press ENTER to submit word (min 3 letters)", "• Press ENTER to submit word (min 3 letters)",
"• Press R to reset game", "• Press R to reset game",
"• Score increases exponentially", "• Score increases exponentially",
"• Keyboard randomizes after each valid word!" "• Keyboard randomizes after each valid word!",
] ]
start_x = 750 start_x = 750
for i, instruction in enumerate(instructions): for i, instruction in enumerate(instructions):
inst_text = self.small_font.render(instruction, True, TEXT_COLOR) inst_text = self.small_font.render(instruction, True, TEXT_COLOR)
self.screen.blit(inst_text, (start_x, 20 + i * 22)) self.screen.blit(inst_text, (start_x, 20 + i * 22))
# Enter button - smaller and repositioned # Enter button - smaller and repositioned
enter_rect = pygame.Rect(750, 190, 120, 40) enter_rect = pygame.Rect(750, 190, 120, 40)
pygame.draw.rect(self.screen, KEY_COLOR, enter_rect) pygame.draw.rect(self.screen, KEY_COLOR, enter_rect)
@ -320,7 +411,7 @@ class KeyboardCoopGame:
enter_text = self.small_font.render("ENTER", True, TEXT_COLOR) enter_text = self.small_font.render("ENTER", True, TEXT_COLOR)
enter_text_rect = enter_text.get_rect(center=enter_rect.center) enter_text_rect = enter_text.get_rect(center=enter_rect.center)
self.screen.blit(enter_text, enter_text_rect) self.screen.blit(enter_text, enter_text_rect)
# Reset button - smaller and repositioned # Reset button - smaller and repositioned
reset_rect = pygame.Rect(880, 190, 120, 40) reset_rect = pygame.Rect(880, 190, 120, 40)
pygame.draw.rect(self.screen, KEY_COLOR, reset_rect) pygame.draw.rect(self.screen, KEY_COLOR, reset_rect)
@ -328,29 +419,29 @@ class KeyboardCoopGame:
reset_text = self.small_font.render("RESET", True, TEXT_COLOR) reset_text = self.small_font.render("RESET", True, TEXT_COLOR)
reset_text_rect = reset_text.get_rect(center=reset_rect.center) reset_text_rect = reset_text.get_rect(center=reset_rect.center)
self.screen.blit(reset_text, reset_text_rect) self.screen.blit(reset_text, reset_text_rect)
return enter_rect, reset_rect return enter_rect, reset_rect
def handle_click(self, pos): def handle_click(self, pos):
"""Handle mouse clicks""" """Handle mouse clicks"""
# Check if clicked on a key # Check if clicked on a key
key = self.get_key_at_position(pos) key = self.get_key_at_position(pos)
if key: if key:
self.handle_letter_click(key) self.handle_letter_click(key)
# Check UI buttons # Check UI buttons
enter_rect = pygame.Rect(750, 190, 120, 40) enter_rect = pygame.Rect(750, 190, 120, 40)
reset_rect = pygame.Rect(880, 190, 120, 40) reset_rect = pygame.Rect(880, 190, 120, 40)
if enter_rect.collidepoint(pos): if enter_rect.collidepoint(pos):
self.submit_word() self.submit_word()
elif reset_rect.collidepoint(pos): elif reset_rect.collidepoint(pos):
self.reset_game() self.reset_game()
def run(self): def run(self):
"""Main game loop""" """Main game loop"""
running = True running = True
while running: while running:
for event in pygame.event.get(): for event in pygame.event.get():
if event.type == pygame.QUIT: if event.type == pygame.QUIT:
@ -368,21 +459,22 @@ class KeyboardCoopGame:
key_name = pygame.key.name(event.key) key_name = pygame.key.name(event.key)
if len(key_name) == 1 and key_name.isalpha(): if len(key_name) == 1 and key_name.isalpha():
self.handle_letter_click(key_name.lower()) self.handle_letter_click(key_name.lower())
# Clear screen # Clear screen
self.screen.fill(BACKGROUND_COLOR) self.screen.fill(BACKGROUND_COLOR)
# Draw everything # Draw everything
self.draw_keyboard() self.draw_keyboard()
self.draw_ui() self.draw_ui()
# Update display # Update display
pygame.display.flip() pygame.display.flip()
self.clock.tick(60) self.clock.tick(60)
pygame.quit() pygame.quit()
sys.exit() sys.exit()
if __name__ == "__main__": if __name__ == "__main__":
game = KeyboardCoopGame() game = KeyboardCoopGame()
game.run() game.run()

View File

@ -370099,4 +370099,4 @@
"zwitter": 1, "zwitter": 1,
"zwitterion": 1, "zwitterion": 1,
"zwitterionic": 1 "zwitterionic": 1
} }

View File

@ -1 +1 @@
43 43

View File

@ -27,10 +27,10 @@ pip install -r PYTHON/lichess_bot/requirements.txt
## Activate BOT and get a token ## Activate BOT and get a token
1) Create or use an existing Lichess account for your bot. 1. Create or use an existing Lichess account for your bot.
2) Activate it as a BOT (one-time): https://lichess.org/api#tag/Bot 2. Activate it as a BOT (one-time): https://lichess.org/api#tag/Bot
- If not already BOT, you need to convert the account; follow Lichess docs. - If not already BOT, you need to convert the account; follow Lichess docs.
3) Create a personal API token: https://lichess.org/account/oauth/token/create 3. Create a personal API token: https://lichess.org/account/oauth/token/create
- Grant scopes: bot:play, challenge:read, challenge:write - Grant scopes: bot:play, challenge:read, challenge:write
Export the token in your shell (recommended): Export the token in your shell (recommended):
@ -71,4 +71,4 @@ bash PYTHON/lichess_bot/run.sh
python -m pytest PYTHON/lichess_bot/tests -q python -m pytest PYTHON/lichess_bot/tests -q
``` ```
If you add tests requiring third-party packages, install them in your environment first. If you add tests requiring third-party packages, install them in your environment first.

View File

@ -1,2 +1,3 @@
"""Package marker for lichess_bot.""" """Package marker for lichess_bot."""
__all__ = [] __all__ = []

View File

@ -1,15 +1,11 @@
import os import os
import shutil
import subprocess import subprocess
import logging
from typing import Optional, Tuple
import chess import chess
class RandomEngine: class RandomEngine:
""" """Thin wrapper around the C engine in C/lichess_random_engine/random_engine.
Thin wrapper around the C engine in C/lichess_random_engine/random_engine.
Contract: Contract:
- Given a chess.Board, call the C binary with all legal moves encoded as - Given a chess.Board, call the C binary with all legal moves encoded as
@ -20,7 +16,13 @@ class RandomEngine:
- If the binary is missing or returns an invalid/illegal move, raise. - If the binary is missing or returns an invalid/illegal move, raise.
""" """
def __init__(self, *, engine_path: Optional[str] = None, max_time_sec: float = 2.0, depth: Optional[int] = None): def __init__(
self,
*,
engine_path: str | None = None,
max_time_sec: float = 2.0,
depth: int | None = None,
):
self.max_time_sec = max_time_sec self.max_time_sec = max_time_sec
# depth is accepted for compatibility with existing callers but is unused; # depth is accepted for compatibility with existing callers but is unused;
# the C engine handles its own scoring/selection. # the C engine handles its own scoring/selection.
@ -29,12 +31,17 @@ class RandomEngine:
default_path = os.path.abspath( default_path = os.path.abspath(
os.path.join( os.path.join(
os.path.dirname(__file__), os.path.dirname(__file__),
"..", "..", "..",
"C", "lichess_random_engine", "random_engine", "..",
"C",
"lichess_random_engine",
"random_engine",
) )
) )
self.engine_path = engine_path or default_path self.engine_path = engine_path or default_path
if not os.path.isfile(self.engine_path) or not os.access(self.engine_path, os.X_OK): if not os.path.isfile(self.engine_path) or not os.access(
self.engine_path, os.X_OK
):
raise FileNotFoundError( raise FileNotFoundError(
f"C engine not found or not executable at '{self.engine_path}'. " f"C engine not found or not executable at '{self.engine_path}'. "
"Build it first (make -C C/lichess_random_engine)." "Build it first (make -C C/lichess_random_engine)."
@ -58,10 +65,14 @@ class RandomEngine:
return out return out
def choose_move(self, board: chess.Board) -> chess.Move: def choose_move(self, board: chess.Board) -> chess.Move:
mv, _ = self.choose_move_with_explanation(board, time_budget_sec=self.max_time_sec) mv, _ = self.choose_move_with_explanation(
board, time_budget_sec=self.max_time_sec
)
return mv return mv
def choose_move_with_explanation(self, board: chess.Board, *, time_budget_sec: float) -> Tuple[Optional[chess.Move], str]: def choose_move_with_explanation(
self, board: chess.Board, *, time_budget_sec: float
) -> tuple[chess.Move | None, str]:
# Collect legal moves and send to engine as plain UCI tokens. # Collect legal moves and send to engine as plain UCI tokens.
legal = list(board.legal_moves) legal = list(board.legal_moves)
if not legal: if not legal:
@ -78,10 +89,14 @@ class RandomEngine:
try: try:
move = chess.Move.from_uci(chosen_uci) move = chess.Move.from_uci(chosen_uci)
except Exception: except Exception:
raise RuntimeError(f"Engine returned invalid move: '{chosen_uci}' (output: {output!r})") raise RuntimeError(
f"Engine returned invalid move: '{chosen_uci}' (output: {output!r})"
)
if move not in board.legal_moves: if move not in board.legal_moves:
raise RuntimeError(f"Engine returned illegal move for position: {chosen_uci}") raise RuntimeError(
f"Engine returned illegal move for position: {chosen_uci}"
)
return move, "from_c_engine" return move, "from_c_engine"
@ -91,9 +106,8 @@ class RandomEngine:
proposed_move_uci: str, proposed_move_uci: str,
*, *,
time_budget_sec: float, time_budget_sec: float,
) -> Tuple[float, str, Optional[chess.Move], str]: ) -> tuple[float, str, chess.Move | None, str]:
""" """Ask the C engine to explain the current move list and analyze a specific candidate.
Ask the C engine to explain the current move list and analyze a specific candidate.
Returns (candidate_score, candidate_expl, best_move, best_expl) Returns (candidate_score, candidate_expl, best_move, best_expl)
where explanations are concise JSON snippets from the engine. All logic is where explanations are concise JSON snippets from the engine. All logic is
@ -103,13 +117,16 @@ class RandomEngine:
if not legal: if not legal:
return 0.0, "no_legal_moves", None, "no_best_move" return 0.0, "no_legal_moves", None, "no_best_move"
args = ["--fen", board.fen(), "--explain", "--analyze", proposed_move_uci] + [m.uci() for m in legal] args = ["--fen", board.fen(), "--explain", "--analyze", proposed_move_uci] + [
m.uci() for m in legal
]
out = self._call_engine(args, timeout=max(0.1, time_budget_sec)) out = self._call_engine(args, timeout=max(0.1, time_budget_sec))
# Try to parse the engine's JSON explanation # Try to parse the engine's JSON explanation
import json as _json import json as _json
cand_score = 0.0 cand_score = 0.0
best_move: Optional[chess.Move] = None best_move: chess.Move | None = None
cand_expl = out cand_expl = out
best_expl = out best_expl = out
try: try:
@ -130,10 +147,13 @@ class RandomEngine:
best_move = None best_move = None
# Store compact explanations for debugging # Store compact explanations for debugging
cand_expl = _json.dumps(analyze, ensure_ascii=False) cand_expl = _json.dumps(analyze, ensure_ascii=False)
best_expl = _json.dumps({ best_expl = _json.dumps(
"chosen_index": data.get("chosen_index"), {
"chosen_move": data.get("chosen_move"), "chosen_index": data.get("chosen_index"),
}, ensure_ascii=False) "chosen_move": data.get("chosen_move"),
},
ensure_ascii=False,
)
except Exception: except Exception:
# Leave defaults with raw output text # Leave defaults with raw output text
pass pass

View File

@ -1,26 +1,29 @@
from collections.abc import Generator
import json import json
import logging import logging
import time import time
from typing import Dict, Generator, Optional, Tuple
import requests
import chess import chess
import requests
LICHESS_API = "https://lichess.org" LICHESS_API = "https://lichess.org"
class LichessAPI: class LichessAPI:
def __init__(self, token: str, session: Optional[requests.Session] = None): def __init__(self, token: str, session: requests.Session | None = None):
self.token = token self.token = token
self.session = session or requests.Session() self.session = session or requests.Session()
self.session.headers.update({ self.session.headers.update(
"Authorization": f"Bearer {self.token}", {
"Accept": "application/json", "Authorization": f"Bearer {self.token}",
"User-Agent": "minimal-lichess-bot/0.1 (+https://lichess.org)" "Accept": "application/json",
}) "User-Agent": "minimal-lichess-bot/0.1 (+https://lichess.org)",
}
)
def _request(self, method: str, url: str, *, raise_for_status: bool = False, **kwargs) -> requests.Response: def _request(
self, method: str, url: str, *, raise_for_status: bool = False, **kwargs
) -> requests.Response:
"""Wrapper around session.request that logs every request/response. """Wrapper around session.request that logs every request/response.
- Logs start (method+URL) and end (status, elapsed). - Logs start (method+URL) and end (status, elapsed).
@ -32,7 +35,7 @@ class LichessAPI:
try: try:
r = self.session.request(method, url, **kwargs) r = self.session.request(method, url, **kwargs)
except Exception as e: except Exception as e:
logging.error(f"HTTP {method} {url} -> exception: {e}") logging.exception(f"HTTP {method} {url} -> exception: {e}")
raise raise
elapsed = time.monotonic() - t0 elapsed = time.monotonic() - t0
status = r.status_code status = r.status_code
@ -45,7 +48,9 @@ class LichessAPI:
except Exception: except Exception:
snippet = None snippet = None
if snippet: if snippet:
logging.warning(f"HTTP {method} {url} -> {status} in {elapsed:.2f}s body='{snippet}'") logging.warning(
f"HTTP {method} {url} -> {status} in {elapsed:.2f}s body='{snippet}'"
)
else: else:
logging.warning(f"HTTP {method} {url} -> {status} in {elapsed:.2f}s") logging.warning(f"HTTP {method} {url} -> {status} in {elapsed:.2f}s")
else: else:
@ -54,14 +59,16 @@ class LichessAPI:
r.raise_for_status() r.raise_for_status()
return r return r
def stream_events(self) -> Generator[Dict, None, None]: def stream_events(self) -> Generator[dict, None, None]:
url = f"{LICHESS_API}/api/stream/event" url = f"{LICHESS_API}/api/stream/event"
backoff = 0.5 backoff = 0.5
while True: while True:
try: try:
# Use NDJSON Accept and no timeout for long-lived stream # Use NDJSON Accept and no timeout for long-lived stream
headers = {"Accept": "application/x-ndjson"} headers = {"Accept": "application/x-ndjson"}
with self._request("GET", url, headers=headers, stream=True, timeout=None) as r: with self._request(
"GET", url, headers=headers, stream=True, timeout=None
) as r:
r.raise_for_status() r.raise_for_status()
backoff = 0.5 # reset on success backoff = 0.5 # reset on success
for line in r.iter_lines(decode_unicode=True): for line in r.iter_lines(decode_unicode=True):
@ -89,7 +96,9 @@ class LichessAPI:
data = {"reason": reason} data = {"reason": reason}
self._request("POST", url, data=data, timeout=30, raise_for_status=True) self._request("POST", url, data=data, timeout=30, raise_for_status=True)
def join_game_stream(self, game_id: str, my_color: Optional[str]) -> Tuple[chess.Board, str]: def join_game_stream(
self, game_id: str, my_color: str | None
) -> tuple[chess.Board, str]:
"""Deprecated: use stream_game_events and parse initial state there.""" """Deprecated: use stream_game_events and parse initial state there."""
# Fallback to initial behavior for compatibility # Fallback to initial behavior for compatibility
url = f"{LICHESS_API}/api/board/game/stream/{game_id}" url = f"{LICHESS_API}/api/board/game/stream/{game_id}"
@ -125,7 +134,7 @@ class LichessAPI:
break break
return board, color return board, color
def stream_game_events(self, game_id: str) -> Generator[Dict, None, None]: def stream_game_events(self, game_id: str) -> Generator[dict, None, None]:
url = f"{LICHESS_API}/api/board/game/stream/{game_id}" url = f"{LICHESS_API}/api/board/game/stream/{game_id}"
headers = {"Accept": "application/x-ndjson"} headers = {"Accept": "application/x-ndjson"}
with self._request("GET", url, headers=headers, stream=True, timeout=None) as r: with self._request("GET", url, headers=headers, stream=True, timeout=None) as r:
@ -151,11 +160,11 @@ class LichessAPI:
r = self._request("POST", url, timeout=30) r = self._request("POST", url, timeout=30)
r.raise_for_status() r.raise_for_status()
def get_game_state(self, game_id: str) -> Optional[Dict]: def get_game_state(self, game_id: str) -> dict | None:
"""Deprecated: use stream_game_events in a persistent loop.""" """Deprecated: use stream_game_events in a persistent loop."""
return None return None
def get_my_user_id(self) -> Optional[str]: def get_my_user_id(self) -> str | None:
url = f"{LICHESS_API}/api/account" url = f"{LICHESS_API}/api/account"
r = self._request("GET", url, timeout=30) r = self._request("GET", url, timeout=30)
if r.status_code == 200: if r.status_code == 200:

View File

@ -2,14 +2,12 @@ import argparse
import json import json
import logging import logging
import os import os
import subprocess
import sys
import threading import threading
import time
from typing import Optional
import chess import chess
import chess.pgn import chess.pgn
import subprocess
import sys
from .engine import RandomEngine from .engine import RandomEngine
from .lichess_api import LichessAPI from .lichess_api import LichessAPI
@ -35,10 +33,10 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No
game_threads = {} game_threads = {}
def handle_game(game_id: str, my_color: Optional[str] = None): def handle_game(game_id: str, my_color: str | None = None):
logging.info(f"Starting game thread for {game_id} [bot v{bot_version}]") logging.info(f"Starting game thread for {game_id} [bot v{bot_version}]")
board = chess.Board() board = chess.Board()
color: Optional[str] = my_color color: str | None = my_color
# Track how many moves we have already processed; start at -1 so we act on the first state (0 moves) # Track how many moves we have already processed; start at -1 so we act on the first state (0 moves)
last_handled_len = -1 last_handled_len = -1
# Prepare a per-game log file # Prepare a per-game log file
@ -54,14 +52,13 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No
opp_ms = None opp_ms = None
inc_ms = 0 inc_ms = 0
# Meta info for logging/PGN # Meta info for logging/PGN
game_date_iso: Optional[str] = None game_date_iso: str | None = None
white_name: Optional[str] = None white_name: str | None = None
black_name: Optional[str] = None black_name: str | None = None
site_url: Optional[str] = None site_url: str | None = None
try: try:
# Only send moves on authoritative gameState events to avoid race # Only send moves on authoritative gameState events to avoid race
# conditions right after gameFull arrives. # conditions right after gameFull arrives.
seen_game_full = False
for event in api.stream_game_events(game_id): for event in api.stream_game_events(game_id):
et = event.get("type") et = event.get("type")
if et in ("gameFull", "gameState"): if et in ("gameFull", "gameState"):
@ -71,8 +68,16 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No
moves = state.get("moves", "") moves = state.get("moves", "")
status = state.get("status") status = state.get("status")
# clocks are in milliseconds if present # clocks are in milliseconds if present
my_ms = state.get("wtime") if color == "white" else state.get("btime") my_ms = (
opp_ms = state.get("btime") if color == "white" else state.get("wtime") state.get("wtime")
if color == "white"
else state.get("btime")
)
opp_ms = (
state.get("btime")
if color == "white"
else state.get("wtime")
)
inc_ms = state.get("winc") or state.get("binc") or 0 inc_ms = state.get("winc") or state.get("binc") or 0
# Discover my color from gameFull # Discover my color from gameFull
white_id = event["white"].get("id") white_id = event["white"].get("id")
@ -82,10 +87,15 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No
# Set site and date if available # Set site and date if available
try: try:
# Lichess event may include 'createdAt' ms epoch # Lichess event may include 'createdAt' ms epoch
created_ms = event.get("createdAt") or event.get("createdAtDate") created_ms = event.get("createdAt") or event.get(
"createdAtDate"
)
if created_ms: if created_ms:
import datetime import datetime
game_date_iso = datetime.datetime.utcfromtimestamp(int(created_ms)/1000).strftime("%Y.%m.%d")
game_date_iso = datetime.datetime.utcfromtimestamp(
int(created_ms) / 1000
).strftime("%Y.%m.%d")
except Exception: except Exception:
pass pass
site_url = f"https://lichess.org/{game_id}" site_url = f"https://lichess.org/{game_id}"
@ -95,7 +105,6 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No
elif me == black_id: elif me == black_id:
color = "black" color = "black"
logging.info(f"Game {game_id}: joined as {color} (gameFull)") logging.info(f"Game {game_id}: joined as {color} (gameFull)")
seen_game_full = True
else: else:
moves = event.get("moves", "") moves = event.get("moves", "")
status = event.get("status") status = event.get("status")
@ -115,7 +124,9 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No
f"Game {game_id}: event={et}, moves={new_len}, color={color}" f"Game {game_id}: event={et}, moves={new_len}, color={color}"
) )
if new_len == last_handled_len: if new_len == last_handled_len:
logging.debug(f"Game {game_id}: position unchanged (len={new_len}), skipping") logging.debug(
f"Game {game_id}: position unchanged (len={new_len}), skipping"
)
continue continue
# Rebuild board from moves # Rebuild board from moves
@ -127,14 +138,18 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No
logging.debug(f"Game {game_id}: could not apply move {m}") logging.debug(f"Game {game_id}: could not apply move {m}")
if color is None: if color is None:
logging.info(f"Game {game_id}: color unknown yet; waiting for gameFull") logging.info(
f"Game {game_id}: color unknown yet; waiting for gameFull"
)
# Do not mark this position handled on gameFull; wait for authoritative gameState # Do not mark this position handled on gameFull; wait for authoritative gameState
if et == "gameState": if et == "gameState":
last_handled_len = new_len last_handled_len = new_len
continue continue
is_white_turn = board.turn is_white_turn = board.turn
my_turn = (is_white_turn and color == "white") or ((not is_white_turn) and color == "black") my_turn = (is_white_turn and color == "white") or (
(not is_white_turn) and color == "black"
)
logging.info( logging.info(
f"Game {game_id}: turn={'white' if is_white_turn else 'black'}, my_turn={my_turn}" f"Game {game_id}: turn={'white' if is_white_turn else 'black'}, my_turn={my_turn}"
) )
@ -142,35 +157,54 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No
# - Always move on 'gameState' (authoritative) # - Always move on 'gameState' (authoritative)
# - Also allow moving on the initial 'gameFull' when there are zero moves and it's our turn. # - Also allow moving on the initial 'gameFull' when there are zero moves and it's our turn.
# This avoids stalling at game start when Lichess doesn't immediately send a 'gameState' for 0 moves. # This avoids stalling at game start when Lichess doesn't immediately send a 'gameState' for 0 moves.
allow_move = (et == "gameState") or (et == "gameFull" and new_len == 0) allow_move = (et == "gameState") or (
et == "gameFull" and new_len == 0
)
if my_turn and allow_move: if my_turn and allow_move:
# Compute a per-move time budget (seconds) based on remaining time # Compute a per-move time budget (seconds) based on remaining time
# Heuristic: use min( max_time_sec, max(0.05, 0.6 * my_time_left/remaining_moves + inc) ) # Heuristic: use min( max_time_sec, max(0.05, 0.6 * my_time_left/remaining_moves + inc) )
# Estimate remaining moves as 30 - ply/2 bounded to [10, 60] # Estimate remaining moves as 30 - ply/2 bounded to [10, 60]
est_moves_left = max(10, min(60, 30 - board.fullmove_number // 2)) est_moves_left = max(
10, min(60, 30 - board.fullmove_number // 2)
)
time_left_sec = (my_ms or 0) / 1000.0 time_left_sec = (my_ms or 0) / 1000.0
inc_sec = (inc_ms or 0) / 1000.0 inc_sec = (inc_ms or 0) / 1000.0
budget = 0.6 * (time_left_sec / max(1, est_moves_left)) + 0.5 * inc_sec budget = (
0.6 * (time_left_sec / max(1, est_moves_left))
+ 0.5 * inc_sec
)
# Spend more time per move (requested): double the budget # Spend more time per move (requested): double the budget
budget *= 2.0 budget *= 2.0
# Keep within reasonable bounds # Keep within reasonable bounds
budget = max(0.05, min(engine.max_time_sec, budget)) budget = max(0.05, min(engine.max_time_sec, budget))
move, reason = engine.choose_move_with_explanation(board, time_budget_sec=budget) move, reason = engine.choose_move_with_explanation(
board, time_budget_sec=budget
)
if move is None: if move is None:
logging.info(f"Game {game_id}: no legal moves (game likely over)") logging.info(
f"Game {game_id}: no legal moves (game likely over)"
)
break break
try: try:
# Double-check legality just before sending to avoid 400s when state changed. # Double-check legality just before sending to avoid 400s when state changed.
if move not in board.legal_moves: if move not in board.legal_moves:
logging.info(f"Game {game_id}: selected move no longer legal; skipping send") logging.info(
f"Game {game_id}: selected move no longer legal; skipping send"
)
else: else:
logging.info(f"Game {game_id}: playing {move.uci()} (budget={budget:.2f}s, my_time_left={time_left_sec:.1f}s, inc={inc_sec:.2f}s)") logging.info(
f"Game {game_id}: playing {move.uci()} (budget={budget:.2f}s, my_time_left={time_left_sec:.1f}s, inc={inc_sec:.2f}s)"
)
if game_log_path: if game_log_path:
with open(game_log_path, "a") as lf: with open(game_log_path, "a") as lf:
lf.write(f"ply {last_handled_len+1}: {move.uci()}\n{reason}\n\n") lf.write(
f"ply {last_handled_len+1}: {move.uci()}\n{reason}\n\n"
)
api.make_move(game_id, move) api.make_move(game_id, move)
except Exception as e: except Exception as e:
logging.warning(f"Game {game_id}: move {move.uci()} failed: {e}") logging.warning(
f"Game {game_id}: move {move.uci()} failed: {e}"
)
# Mark this position as handled on authoritative gameState, or after we've # Mark this position as handled on authoritative gameState, or after we've
# actually attempted a move (including the first move on gameFull len=0). # actually attempted a move (including the first move on gameFull len=0).
if et == "gameState" or (my_turn and allow_move): if et == "gameState" or (my_turn and allow_move):
@ -178,9 +212,7 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No
if status in {"mate", "resign", "stalemate", "timeout", "draw"}: if status in {"mate", "resign", "stalemate", "timeout", "draw"}:
logging.info(f"Game {game_id} finished: {status}") logging.info(f"Game {game_id} finished: {status}")
break break
elif et == "chatLine": elif et == "chatLine" or et == "opponentGone":
continue
elif et == "opponentGone":
continue continue
except Exception as e: except Exception as e:
logging.exception(f"Game {game_id} thread error: {e}") logging.exception(f"Game {game_id} thread error: {e}")
@ -204,12 +236,14 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No
pass pass
with open(game_log_path, "a") as lf: with open(game_log_path, "a") as lf:
lf.write("\nPGN:\n") lf.write("\nPGN:\n")
exporter = chess.pgn.StringExporter(headers=True, variations=False, comments=False) exporter = chess.pgn.StringExporter(
headers=True, variations=False, comments=False
)
lf.write(game.accept(exporter)) lf.write(game.accept(exporter))
lf.write("\n") lf.write("\n")
# After PGN is written, run analysis and save it to the same file (inserted before PGN) # After PGN is written, run analysis and save it to the same file (inserted before PGN)
if game_log_path: if game_log_path:
analysis_text: Optional[str] = None analysis_text: str | None = None
try: try:
analyze_script = os.path.join( analyze_script = os.path.join(
os.path.dirname(os.path.dirname(__file__)), os.path.dirname(os.path.dirname(__file__)),
@ -245,7 +279,11 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No
if m: if m:
# Count as one analyzed ply # Count as one analyzed ply
analyzed += 1 analyzed += 1
left = max(0, (total_plies or 0) - analyzed) if total_plies else "?" left = (
max(0, (total_plies or 0) - analyzed)
if total_plies
else "?"
)
if total_plies: if total_plies:
pct = analyzed / total_plies * 100.0 pct = analyzed / total_plies * 100.0
logging.info( logging.info(
@ -266,7 +304,7 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No
f"Game {game_id}: analysis script exited with code {ret}" f"Game {game_id}: analysis script exited with code {ret}"
) )
if stderr_text: if stderr_text:
analysis_text += ("\n[stderr]\n" + stderr_text) analysis_text += "\n[stderr]\n" + stderr_text
logging.info(f"Game {game_id}: analysis complete") logging.info(f"Game {game_id}: analysis complete")
else: else:
logging.info( logging.info(
@ -278,14 +316,18 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No
# Insert analysis before the PGN section so future runs can still parse PGN cleanly # Insert analysis before the PGN section so future runs can still parse PGN cleanly
if analysis_text: if analysis_text:
try: try:
with open(game_log_path, "r", encoding="utf-8", errors="replace") as f: with open(
game_log_path, encoding="utf-8", errors="replace"
) as f:
content = f.read() content = f.read()
# Find the start of the 'PGN:' line # Find the start of the 'PGN:' line
insert_idx = 0 insert_idx = 0
p = content.find("\nPGN:\n") p = content.find("\nPGN:\n")
if p != -1: if p != -1:
insert_idx = p + 1 # start of the line after the preceding newline insert_idx = (
p + 1
) # start of the line after the preceding newline
elif content.startswith("PGN:\n"): elif content.startswith("PGN:\n"):
insert_idx = 0 insert_idx = 0
else: else:
@ -297,21 +339,31 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No
if game_date_iso: if game_date_iso:
meta_lines.append(f"Date: {game_date_iso}") meta_lines.append(f"Date: {game_date_iso}")
if white_name or black_name: if white_name or black_name:
meta_lines.append(f"Players: {white_name or '?'} vs {black_name or '?'}") meta_lines.append(
f"Players: {white_name or '?'} vs {black_name or '?'}"
)
if meta_lines: if meta_lines:
meta_block = "\n".join(meta_lines) + "\n" meta_block = "\n".join(meta_lines) + "\n"
else: else:
meta_block = "" meta_block = ""
analysis_block = ( analysis_block = (
(meta_block if meta_block else "") + (meta_block if meta_block else "")
"ANALYSIS:\n" + analysis_text.rstrip() + "\n\n" + "ANALYSIS:\n"
+ analysis_text.rstrip()
+ "\n\n"
)
new_content = (
content[:insert_idx]
+ analysis_block
+ content[insert_idx:]
) )
new_content = content[:insert_idx] + analysis_block + content[insert_idx:]
with open(game_log_path, "w", encoding="utf-8") as f: with open(game_log_path, "w", encoding="utf-8") as f:
f.write(new_content) f.write(new_content)
except Exception as e: except Exception as e:
logging.debug(f"Game {game_id}: could not write analysis to log: {e}") logging.debug(
f"Game {game_id}: could not write analysis to log: {e}"
)
except Exception as e: except Exception as e:
logging.debug(f"Game {game_id}: could not write PGN: {e}") logging.debug(f"Game {game_id}: could not write PGN: {e}")
logging.info(f"Ending game thread for {game_id}") logging.info(f"Ending game thread for {game_id}")
@ -328,19 +380,29 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No
variant = challenge.get("variant", {}).get("key", "standard") variant = challenge.get("variant", {}).get("key", "standard")
speed = challenge.get("speed") speed = challenge.get("speed")
perf_ok = speed in {"bullet", "blitz", "rapid", "classical"} perf_ok = speed in {"bullet", "blitz", "rapid", "classical"}
not_corr = challenge.get("speed") != "correspondence" or not decline_correspondence not_corr = (
challenge.get("speed") != "correspondence"
or not decline_correspondence
)
if variant == "standard" and perf_ok and not_corr: if variant == "standard" and perf_ok and not_corr:
logging.info(f"Accepting challenge {ch_id} ({speed})") logging.info(f"Accepting challenge {ch_id} ({speed})")
api.accept_challenge(ch_id) api.accept_challenge(ch_id)
else: else:
logging.info(f"Declining challenge {ch_id} (variant={variant}, speed={speed})") logging.info(
f"Declining challenge {ch_id} (variant={variant}, speed={speed})"
)
api.decline_challenge(ch_id) api.decline_challenge(ch_id)
elif event.get("type") == "gameStart": elif event.get("type") == "gameStart":
game_id = event["game"]["id"] game_id = event["game"]["id"]
# Spin up a game thread # Spin up a game thread
if game_id not in game_threads or not game_threads[game_id].is_alive(): if (
t = threading.Thread(target=handle_game, args=(game_id,), name=f"game-{game_id}") game_id not in game_threads
or not game_threads[game_id].is_alive()
):
t = threading.Thread(
target=handle_game, args=(game_id,), name=f"game-{game_id}"
)
t.daemon = True t.daemon = True
game_threads[game_id] = t game_threads[game_id] = t
t.start() t.start()
@ -359,8 +421,14 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No
def main(): def main():
parser = argparse.ArgumentParser(description="Run a minimal Lichess bot") parser = argparse.ArgumentParser(description="Run a minimal Lichess bot")
parser.add_argument("--log-level", default="INFO", help="Logging level (default: INFO)") parser.add_argument(
parser.add_argument("--decline-correspondence", action="store_true", help="Decline correspondence challenges") "--log-level", default="INFO", help="Logging level (default: INFO)"
)
parser.add_argument(
"--decline-correspondence",
action="store_true",
help="Decline correspondence challenges",
)
args = parser.parse_args() args = parser.parse_args()
run_bot(args.log_level, args.decline_correspondence) run_bot(args.log_level, args.decline_correspondence)

View File

@ -1,6 +1,6 @@
python-chess==1.999
requests==2.32.3
urllib3==2.2.3
certifi>=2024.2.2 certifi>=2024.2.2
chardet>=5.2.0 chardet>=5.2.0
idna>=3.7 idna>=3.7
python-chess==1.999
requests==2.32.3
urllib3==2.2.3

View File

@ -1,6 +1,6 @@
import os import os
import sys
from pathlib import Path from pathlib import Path
import sys
# Add repository root to sys.path so 'import PYTHON.*' works when running # Add repository root to sys.path so 'import PYTHON.*' works when running
# pytest with a subdirectory as rootdir. # pytest with a subdirectory as rootdir.

View File

@ -1,6 +1,5 @@
import csv import csv
import os import os
from typing import List, Tuple
import chess import chess
import pytest import pytest
@ -8,12 +7,11 @@ import pytest
from PYTHON.lichess_bot.engine import RandomEngine from PYTHON.lichess_bot.engine import RandomEngine
def _load_top_puzzles(csv_path: str, limit: int = 8) -> List[Tuple[str, str]]: def _load_top_puzzles(csv_path: str, limit: int = 8) -> list[tuple[str, str]]:
""" """Return a list of (FEN, solution_moves_str) for the first `limit` rows in the CSV.
Return a list of (FEN, solution_moves_str) for the first `limit` rows in the CSV.
CSV columns: PuzzleId,FEN,Moves,... CSV columns: PuzzleId,FEN,Moves,...
""" """
puzzles: List[Tuple[str, str]] = [] puzzles: list[tuple[str, str]] = []
with open(csv_path, newline="", encoding="utf-8") as f: with open(csv_path, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f) reader = csv.DictReader(f)
for row in reader: for row in reader:
@ -28,7 +26,9 @@ def _load_top_puzzles(csv_path: str, limit: int = 8) -> List[Tuple[str, str]]:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"fen,moves_str", "fen,moves_str",
_load_top_puzzles(os.path.join(os.path.dirname(__file__), "lichess_db_puzzle.csv"), limit=8), _load_top_puzzles(
os.path.join(os.path.dirname(__file__), "lichess_db_puzzle.csv"), limit=8
),
) )
def test_puzzle_engine_follow_solution(fen: str, moves_str: str): def test_puzzle_engine_follow_solution(fen: str, moves_str: str):
board = chess.Board(fen) board = chess.Board(fen)
@ -46,7 +46,11 @@ def test_puzzle_engine_follow_solution(fen: str, moves_str: str):
# If engine move differs from solution, fail immediately but provide analysis of the correct move # If engine move differs from solution, fail immediately but provide analysis of the correct move
if mv.uci() != uci: if mv.uci() != uci:
# Ask the engine to analyze the correct move for debug # Ask the engine to analyze the correct move for debug
score_cp, proposed_expl, best_mv, best_expl = eng.evaluate_proposed_move_with_suggestion(board, uci, time_budget_sec=0.5) score_cp, proposed_expl, best_mv, best_expl = (
eng.evaluate_proposed_move_with_suggestion(
board, uci, time_budget_sec=0.5
)
)
details = [ details = [
f"Puzzle failed at step {step}.", f"Puzzle failed at step {step}.",
f"FEN: {fen}", f"FEN: {fen}",

View File

@ -1,5 +1,3 @@
import builtins
from PYTHON.lichess_bot.utils import backoff_sleep from PYTHON.lichess_bot.utils import backoff_sleep

View File

@ -1,6 +1,3 @@
import os
import tempfile
from PYTHON.lichess_bot.utils import get_and_increment_version from PYTHON.lichess_bot.utils import get_and_increment_version
@ -15,5 +12,5 @@ def test_version_file_increments_and_persists(tmp_path, monkeypatch):
assert v2 == 2 assert v2 == 2
# Ensure it persisted # Ensure it persisted
with open(version_file, "r") as f: with open(version_file) as f:
assert f.read().strip() == "2" assert f.read().strip() == "2"

96
PYTHON/lichess_bot/tools/generate_blunder_tests.py Normal file → Executable file
View File

@ -1,6 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """Generate pytest cases from one or more lichess analysis logs.
Generate pytest cases from one or more lichess analysis logs.
Input: log files that contain a "Columns:" section and a "PGN:" section. Input: log files that contain a "Columns:" section and a "PGN:" section.
We'll extract each row where class==Blunder, reconstruct the FEN of the We'll extract each row where class==Blunder, reconstruct the FEN of the
@ -33,12 +32,11 @@ Dependencies: python-chess, pytest (already in requirements.txt)
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
import io import io
import os import os
import re import re
import sys import sys
from dataclasses import dataclass
from typing import List, Tuple
import chess import chess
import chess.pgn import chess.pgn
@ -48,11 +46,11 @@ import chess.pgn
class Blunder: class Blunder:
ply: int ply: int
side: str # 'W' or 'B' side: str # 'W' or 'B'
san: str # SAN of the played blunder san: str # SAN of the played blunder
best_suggestion_san: str # SAN of the best suggestion from log (mandatory) best_suggestion_san: str # SAN of the best suggestion from log (mandatory)
def parse_columns_for_blunders(text: str) -> List[Blunder]: def parse_columns_for_blunders(text: str) -> list[Blunder]:
lines = text.splitlines() lines = text.splitlines()
# Find start of "Columns:" block # Find start of "Columns:" block
try: try:
@ -60,9 +58,9 @@ def parse_columns_for_blunders(text: str) -> List[Blunder]:
except StopIteration: except StopIteration:
return [] return []
blunders: List[Blunder] = [] blunders: list[Blunder] = []
# Lines after header until a blank line or "PGN:" marker # Lines after header until a blank line or "PGN:" marker
for ln in lines[idx + 1:]: for ln in lines[idx + 1 :]:
if not ln.strip(): if not ln.strip():
break break
if ln.strip().startswith("PGN:"): if ln.strip().startswith("PGN:"):
@ -90,13 +88,20 @@ def parse_columns_for_blunders(text: str) -> List[Blunder]:
f"Missing best_suggestion in Columns for blunder row: ply={ply} side={side} move={move_san}.\n" f"Missing best_suggestion in Columns for blunder row: ply={ply} side={side} move={move_san}.\n"
f"Raw line: '{ln.strip()}'" f"Raw line: '{ln.strip()}'"
) )
blunders.append(Blunder(ply=ply, side=side, san=move_san, best_suggestion_san=best_suggestion_san)) blunders.append(
Blunder(
ply=ply,
side=side,
san=move_san,
best_suggestion_san=best_suggestion_san,
)
)
return blunders return blunders
def extract_pgn(text: str) -> str | None: def extract_pgn(text: str) -> str | None:
# Extract the PGN block after a line that is exactly 'PGN:' or starts with it # Extract the PGN block after a line that is exactly 'PGN:' or starts with it
m = re.search(r"^PGN:\s*$", text, flags=re.M) m = re.search(r"^PGN:\s*$", text, flags=re.MULTILINE)
if not m: if not m:
return None return None
start = m.end() start = m.end()
@ -104,8 +109,8 @@ def extract_pgn(text: str) -> str | None:
return pgn if pgn else None return pgn if pgn else None
def san_list_from_game(game: chess.pgn.Game) -> List[str]: def san_list_from_game(game: chess.pgn.Game) -> list[str]:
san_moves: List[str] = [] san_moves: list[str] = []
node = game node = game
while node.variations: while node.variations:
node = node.variation(0) node = node.variation(0)
@ -113,13 +118,15 @@ def san_list_from_game(game: chess.pgn.Game) -> List[str]:
return san_moves return san_moves
def fen_and_uci_for_blunders(pgn_text: str, blunders: List[Blunder]) -> List[Tuple[str, str, str, Blunder]]: def fen_and_uci_for_blunders(
pgn_text: str, blunders: list[Blunder]
) -> list[tuple[str, str, str, Blunder]]:
game = chess.pgn.read_game(io.StringIO(pgn_text)) game = chess.pgn.read_game(io.StringIO(pgn_text))
if game is None: if game is None:
raise RuntimeError("Failed to parse PGN from log") raise RuntimeError("Failed to parse PGN from log")
main_sans = san_list_from_game(game) main_sans = san_list_from_game(game)
results: List[Tuple[str, str, str, Blunder]] = [] results: list[tuple[str, str, str, Blunder]] = []
for bl in blunders: for bl in blunders:
# Reconstruct the board before this ply # Reconstruct the board before this ply
board = game.board() board = game.board()
@ -198,17 +205,25 @@ def test_engine_avoids_logged_blunder(fen, blunder_uci, label):
) )
def append_cases_to_unified_test(unified_path: str, cases: List[Tuple[str, str, str, Blunder]]) -> int: def append_cases_to_unified_test(
unified_path: str, cases: list[tuple[str, str, str, Blunder]]
) -> int:
"""Append new cases to BLUNDER_CASES in the unified test file, skipping duplicates. """Append new cases to BLUNDER_CASES in the unified test file, skipping duplicates.
Returns the number of cases actually appended. Returns the number of cases actually appended.
""" """
ensure_unified_test_file(unified_path) ensure_unified_test_file(unified_path)
with open(unified_path, "r", encoding="utf-8") as f: with open(unified_path, encoding="utf-8") as f:
content = f.read() content = f.read()
# Extract current cases as a set of (fen, uci) to de-duplicate # Extract current cases as a set of (fen, uci) to de-duplicate
existing = set(re.findall(r"\(\"(.*?)\",\s*\"(.*?)\",\s*\"ply\d+_[WB]_[^\"]+\"\)\,?", content, flags=re.S)) existing = set(
re.findall(
r"\(\"(.*?)\",\s*\"(.*?)\",\s*\"ply\d+_[WB]_[^\"]+\"\)\,?",
content,
flags=re.DOTALL,
)
)
lines = [] lines = []
updated_existing = 0 updated_existing = 0
@ -217,7 +232,7 @@ def append_cases_to_unified_test(unified_path: str, cases: List[Tuple[str, str,
if key in existing: if key in existing:
# If a best move UCI is available, try to backfill or update it into the label # If a best move UCI is available, try to backfill or update it into the label
if best_uci: if best_uci:
side = 'W' if bl.side == 'W' else 'B' side = "W" if bl.side == "W" else "B"
fen_re = re.escape(fen) fen_re = re.escape(fen)
uci_re = re.escape(uci) uci_re = re.escape(uci)
base_label = f"ply{bl.ply}_{side}_{uci}" base_label = f"ply{bl.ply}_{side}_{uci}"
@ -228,7 +243,9 @@ def append_cases_to_unified_test(unified_path: str, cases: List[Tuple[str, str,
if re.search(pattern_no_best, content): if re.search(pattern_no_best, content):
content = re.sub( content = re.sub(
pattern_no_best, pattern_no_best,
lambda m: m.group(0).replace(m.group(1), f"{base_label}_best_{best_uci}"), lambda m: m.group(0).replace(
m.group(1), f"{base_label}_best_{best_uci}"
),
content, content,
count=1, count=1,
) )
@ -236,7 +253,9 @@ def append_cases_to_unified_test(unified_path: str, cases: List[Tuple[str, str,
elif re.search(pattern_with_best, content): elif re.search(pattern_with_best, content):
content = re.sub( content = re.sub(
pattern_with_best, pattern_with_best,
lambda m: m.group(0).replace(m.group(1), f"{base_label}_best_{best_uci}"), lambda m: m.group(0).replace(
m.group(1), f"{base_label}_best_{best_uci}"
),
content, content,
count=1, count=1,
) )
@ -245,7 +264,7 @@ def append_cases_to_unified_test(unified_path: str, cases: List[Tuple[str, str,
label = f"ply{bl.ply}_{'W' if bl.side=='W' else 'B'}_{uci}" label = f"ply{bl.ply}_{'W' if bl.side=='W' else 'B'}_{uci}"
# Encode the best move UCI in the label so tests can extract it without changing tuple shape # Encode the best move UCI in the label so tests can extract it without changing tuple shape
label += f"_best_{best_uci}" label += f"_best_{best_uci}"
lines.append(f" (\"{fen}\", \"{uci}\", \"{label}\"),\n") lines.append(f' ("{fen}", "{uci}", "{label}"),\n')
if not lines: if not lines:
return 0 return 0
@ -267,7 +286,7 @@ def append_cases_to_unified_test(unified_path: str, cases: List[Tuple[str, str,
def _process_single_log(log_path: str) -> int: def _process_single_log(log_path: str) -> int:
"""Process a single log file. Returns 0 on success, non-zero otherwise.""" """Process a single log file. Returns 0 on success, non-zero otherwise."""
try: try:
with open(log_path, "r", encoding="utf-8") as fh: with open(log_path, encoding="utf-8") as fh:
text = fh.read() text = fh.read()
except FileNotFoundError: except FileNotFoundError:
print(f"Log file not found: {log_path}") print(f"Log file not found: {log_path}")
@ -293,7 +312,9 @@ def _process_single_log(log_path: str) -> int:
print(f"Error converting SAN to UCI in {os.path.basename(log_path)}: {e}") print(f"Error converting SAN to UCI in {os.path.basename(log_path)}: {e}")
return 2 return 2
if not cases: if not cases:
print(f"Failed to reconstruct any blunder positions from PGN: {os.path.basename(log_path)}") print(
f"Failed to reconstruct any blunder positions from PGN: {os.path.basename(log_path)}"
)
return 1 return 1
base = os.path.basename(log_path) base = os.path.basename(log_path)
@ -301,14 +322,18 @@ def _process_single_log(log_path: str) -> int:
game_id = m.group(1) if m else os.path.splitext(base)[0] game_id = m.group(1) if m else os.path.splitext(base)[0]
# Always append to the unified test file # Always append to the unified test file
unified = os.path.join(os.path.dirname(__file__), "..", "tests", "test_blunders_all.py") unified = os.path.join(
os.path.dirname(__file__), "..", "tests", "test_blunders_all.py"
)
unified = os.path.abspath(unified) unified = os.path.abspath(unified)
added = append_cases_to_unified_test(unified, cases) added = append_cases_to_unified_test(unified, cases)
print(f"Appended {added} new blunder checks to {os.path.relpath(unified)} (game {game_id}).") print(
f"Appended {added} new blunder checks to {os.path.relpath(unified)} (game {game_id})."
)
return 0 return 0
def main(argv: List[str]) -> int: def main(argv: list[str]) -> int:
script_dir = os.path.dirname(__file__) script_dir = os.path.dirname(__file__)
past_dir = os.path.abspath(os.path.join(script_dir, "past_games")) past_dir = os.path.abspath(os.path.join(script_dir, "past_games"))
@ -332,7 +357,9 @@ def main(argv: List[str]) -> int:
rc = _process_single_log(lp) rc = _process_single_log(lp)
if rc == 0: if rc == 0:
ok += 1 ok += 1
print(f"Processed {len(logs)} logs from {past_dir}, succeeded: {ok}, failed: {len(logs)-ok}") print(
f"Processed {len(logs)} logs from {past_dir}, succeeded: {ok}, failed: {len(logs)-ok}"
)
return 0 if ok > 0 else 1 return 0 if ok > 0 else 1
# One argument: game id or file path # One argument: game id or file path
@ -340,15 +367,14 @@ def main(argv: List[str]) -> int:
candidate_path = None candidate_path = None
if os.path.isfile(arg): if os.path.isfile(arg):
candidate_path = arg candidate_path = arg
# Treat as game id, resolve within past_games
elif re.fullmatch(r"[A-Za-z0-9]+", arg):
candidate_path = os.path.join(past_dir, f"lichess_bot_game_{arg}.log")
else: else:
# Treat as game id, resolve within past_games # Fallback: if it's a bare filename, try inside past_games
if re.fullmatch(r"[A-Za-z0-9]+", arg): maybe = os.path.join(past_dir, arg)
candidate_path = os.path.join(past_dir, f"lichess_bot_game_{arg}.log") if os.path.isfile(maybe):
else: candidate_path = maybe
# Fallback: if it's a bare filename, try inside past_games
maybe = os.path.join(past_dir, arg)
if os.path.isfile(maybe):
candidate_path = maybe
if not candidate_path: if not candidate_path:
print("Usage: generate_blunder_tests.py [<game_id>|</path/to/log>]") print("Usage: generate_blunder_tests.py [<game_id>|</path/to/log>]")

View File

@ -22,7 +22,7 @@ def get_and_increment_version() -> int:
path = _version_file_path() path = _version_file_path()
current = 0 current = 0
try: try:
with open(path, "r") as f: with open(path) as f:
raw = f.read().strip() raw = f.read().strip()
if raw: if raw:
current = int(raw) current = int(raw)
@ -54,7 +54,7 @@ def backoff_sleep(current_backoff: int, base: float = 0.5, cap: float = 8.0) ->
- base: base delay in seconds - base: base delay in seconds
- cap: maximum delay in seconds - cap: maximum delay in seconds
""" """
delay = min(cap, base * (2 ** current_backoff)) delay = min(cap, base * (2**current_backoff))
logging.info(f"Backing off for {delay:.1f}s") logging.info(f"Backing off for {delay:.1f}s")
time.sleep(delay) time.sleep(delay)
return min(current_backoff + 1, 10) return min(current_backoff + 1, 10)

View File

@ -0,0 +1,54 @@
# Simulate Connection Failure
(it takes ≈ 1hr to get it working)
Install: https://mitmproxy.org/ (install it the way they recommend for your OS, for Ubuntu specifically apt version is 4 main versions behind newest version)
Run it using `mitmproxy`
Run your webbrowser using `127.0.0.1:8080` as proxy
on chromium-based browser it should be enough to do:
`google-chrome --proxy-server=127.0.0.1:8080`
Run `mitmweb`
open 127.0.0.1:808**1**
Click `File` in upper left corner and then `Install Certificates`
You should get a list of Windows/Linux/macOS/Firefox with certificates and how to install them
Install certificates using those instructions
**important!** Go to your browser certificate settings and ensure that:
1. mitmproxy certificate is imported
2. **it is set to trusted**
Now all of your network communication should go through mitmproxy, you can verify it by going to 127.0.0.1:808**1** and seeing constant flow of requests
## What can we do with it?
1. Install mitmproxy python library using pip
`pip install mitmproxy`
2. Copy and paste this “hello world”:
```
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
# Only intercept traffic to example.com
if "example.com" in flow.request.host:
flow.response = http.Response.make(
502, # Bad Gateway status code
b"Simulated connection failure",
{"Content-Type": "text/plain"}
)
```
3. Run it: `mitmdump -s mitm_world.py`
4. Go to [example.com](http://example.com)
5. You should see “simulated connection failure” in plain text

View File

@ -0,0 +1,11 @@
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
# Only intercept traffic to example.com
if "example.com" in flow.request.host:
flow.response = http.Response.make(
502, # Bad Gateway status code
b"Simulated connection failure",
{"Content-Type": "text/plain"},
)

View File

@ -160,4 +160,4 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
*.pdf *.pdf

View File

@ -1,4 +1,4 @@
[pytest] [pytest]
filterwarnings = filterwarnings =
ignore::DeprecationWarning ignore::DeprecationWarning

View File

@ -0,0 +1 @@
Did you ever need to generate random jpg images with huge file size? Now you can!

View File

@ -0,0 +1,133 @@
import argparse
from datetime import datetime
import os
import random
from PIL import Image
def generate_bloated_jpeg(
size, color_list, block_size, output_path, quality, image_index, folder
):
"""Generates a random JPEG image with given size, list of colors, and block size.
Args:
size (int): Size of the image (both width and height, must be divisible by block_size).
color_list (list of str): List of colors in hex format.
block_size (int): Size of the pixel blocks.
output_path (str): Output path for the JPEG image.
quality (int): Quality setting for the JPEG image (0-100).
image_index (int): Index of the image for unique naming.
folder (str): Folder to save the image.
"""
# Ensure size is divisible by block_size and does not exceed 1000 pixels
if size > 1000 or size % block_size != 0:
raise ValueError("Size must be 1000 pixels or less and divisible by block_size")
# Create a new image
image = Image.new("RGB", (size, size))
pixels = image.load()
# Convert hex colors to RGB
rgb_colors = [
tuple(int(color[i : i + 2], 16) for i in (1, 3, 5)) for color in color_list
]
# Fill the image with block_size x block_size pixel squares of random colors from the list
for y in range(0, size, block_size):
for x in range(0, size, block_size):
color = random.choice(rgb_colors)
for i in range(block_size):
for j in range(block_size):
pixels[x + i, y + j] = color
# Create the folder if it does not exist
if not os.path.exists(folder):
os.makedirs(folder)
# Generate unique output path
unique_output_path = os.path.join(
folder,
f"{os.path.splitext(output_path)[0]}_{image_index}{os.path.splitext(output_path)[1]}",
)
# Save the image with specified quality to maximize file size
image.save(unique_output_path, "JPEG", quality=quality, optimize=False)
return unique_output_path
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Generate bloated JPEG images with random colors."
)
parser.add_argument(
"-n",
"--num_images",
type=int,
default=1,
help="Number of images to generate. Default is 1.",
)
parser.add_argument(
"-s",
"--size",
type=int,
default=1000,
help="Size of the images (must be 1000 or less and divisible by block size). Default is 1000.",
)
parser.add_argument(
"-c",
"--colors",
nargs="+",
default=["#FF5733", "#33FF57", "#3357FF", "#F3FF33", "#FF33F6", "#33FFF6"],
help="List of colors in hex format. Default is ['#FF5733', '#33FF57', '#3357FF', '#F3FF33', '#FF33F6', '#33FFF6'].",
)
parser.add_argument(
"-b",
"--block_size",
type=int,
default=4,
help="Size of the pixel blocks (must divide the image size evenly). Default is 4.",
)
parser.add_argument(
"-o",
"--output_path",
type=str,
default="bloated_image.jpeg",
help="Base output path for the JPEG images. Default is 'bloated_image.jpeg'.",
)
parser.add_argument(
"-q",
"--quality",
type=int,
default=100,
help="Quality setting for the JPEG images (0-100). Default is 100.",
)
args = parser.parse_args()
# Create folder named after the current timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
folder = f"generated_images_{timestamp}"
# Display used parameters
print(f"Generating {args.num_images} image(s) with the following parameters:")
print(f" Size: {args.size}")
print(f" Colors: {args.colors}")
print(f" Block size: {args.block_size}")
print(f" Base output path: {args.output_path}")
print(f" Quality: {args.quality}")
print(f" Output folder: {folder}")
# Generate the specified number of images
for i in range(1, args.num_images + 1):
output_path = generate_bloated_jpeg(
args.size,
args.colors,
args.block_size,
args.output_path,
args.quality,
i,
folder,
)
print(f"Image {i} saved to {os.path.abspath(output_path)}")

View File

@ -0,0 +1 @@
pillow

View File

@ -1,6 +1,7 @@
import random import random
import sys
import re import re
import sys
def randomize_numbers(numbers, min_percentage=1, max_percentage=20): def randomize_numbers(numbers, min_percentage=1, max_percentage=20):
randomized_numbers = [] randomized_numbers = []
@ -13,9 +14,10 @@ def randomize_numbers(numbers, min_percentage=1, max_percentage=20):
randomized_numbers.append(new_number) randomized_numbers.append(new_number)
return randomized_numbers return randomized_numbers
def parse_input(input_string): def parse_input(input_string):
# Replace commas with dots and remove non-numeric characters except dots, commas, and digits # Replace commas with dots and remove non-numeric characters except dots, commas, and digits
cleaned_input = re.sub(r'[^\d.,\s]', '', input_string).replace(',', '.') cleaned_input = re.sub(r"[^\d.,\s]", "", input_string).replace(",", ".")
# Split the cleaned input into individual numbers # Split the cleaned input into individual numbers
number_strings = cleaned_input.split() number_strings = cleaned_input.split()
# Convert the number strings to floats # Convert the number strings to floats
@ -24,8 +26,8 @@ def parse_input(input_string):
for num in number_strings: for num in number_strings:
try: try:
float_num = float(num) float_num = float(num)
if '.' in num: if "." in num:
digits_count = len(num.split('.')[-1]) digits_count = len(num.split(".")[-1])
else: else:
digits_count = 0 digits_count = 0
numbers.append(float_num) numbers.append(float_num)
@ -34,13 +36,16 @@ def parse_input(input_string):
continue continue
return numbers, decimal_counts return numbers, decimal_counts
if __name__ == "__main__": if __name__ == "__main__":
if len(sys.argv) < 2: if len(sys.argv) < 2:
print("Usage: python random_digits.py <number1> <number2> ... [min_percentage max_percentage]") print(
"Usage: python random_digits.py <number1> <number2> ... [min_percentage max_percentage]"
)
sys.exit(1) sys.exit(1)
try: try:
input_string = ' '.join(sys.argv[1:]) input_string = " ".join(sys.argv[1:])
numbers, decimal_counts = parse_input(input_string) numbers, decimal_counts = parse_input(input_string)
min_percentage = 1 min_percentage = 1
max_percentage = 20 max_percentage = 20
@ -62,7 +67,7 @@ if __name__ == "__main__":
randomized_numbers = randomize_numbers(numbers, min_percentage, max_percentage) randomized_numbers = randomize_numbers(numbers, min_percentage, max_percentage)
formatted_numbers = [] formatted_numbers = []
for i, num in enumerate(randomized_numbers): for i, num in enumerate(randomized_numbers):
format_str = f'.{decimal_counts[i]}f' format_str = f".{decimal_counts[i]}f"
formatted_numbers.append(float(format(num, format_str))) formatted_numbers.append(float(format(num, format_str)))
print("Original numbers:", numbers) print("Original numbers:", numbers)
@ -71,4 +76,3 @@ if __name__ == "__main__":
print(f"Error: {e}") print(f"Error: {e}")
print("Please provide valid numbers and percentages.") print("Please provide valid numbers and percentages.")
sys.exit(1) sys.exit(1)

63
PYTHON/scapeWebsite/.gitignore vendored Normal file
View File

@ -0,0 +1,63 @@
# JPEG
*.jpg
*.jpeg
*.jpe
*.jif
*.jfif
*.jfi
# JPEG 2000
*.jp2
*.j2k
*.jpf
*.jpx
*.jpm
*.mj2
# JPEG XR
*.jxr
*.hdp
*.wdp
# Graphics Interchange Format
*.gif
# RAW
*.raw
# Web P
*.webp
# Portable Network Graphics
*.png
# Animated Portable Network Graphics
*.apng
# Multiple-image Network Graphics
*.mng
# Tagged Image File Format
*.tiff
*.tif
# Scalable Vector Graphics
*.svg
*.svgz
# Portable Document Format
*.pdf
# X BitMap
*.xbm
# BMP
*.bmp
*.dib
# ICO
*.ico
# 3D Images
*.3dm
*.max

View File

@ -0,0 +1,2 @@
requests
selenium

View File

@ -0,0 +1,75 @@
import argparse
import os
from urllib.parse import urlparse
import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
# Initialize argument parser to accept the website URL as an argument
parser = argparse.ArgumentParser(description="Download images from a comic website.")
parser.add_argument(
"url", type=str, help="The URL of the website to start downloading images from"
)
args = parser.parse_args()
# Initialize WebDriver (Use the appropriate driver for your browser)
driver = webdriver.Chrome()
# Open the website from the passed argument
url = args.url
print(f"Opening the website: {url}")
driver.get(url)
# A function to download images by URL
def download_image(url):
# Extract image name from URL
image_name = os.path.basename(urlparse(url).path)
# Check if the image already exists
if os.path.exists(image_name):
print(f"Image {image_name} already exists, skipping download.")
return False
print(f"Downloading image from URL: {url}")
img_data = requests.get(url).content
with open(image_name, "wb") as handler:
handler.write(img_data)
print(f"Image {image_name} downloaded successfully")
return True
# No need to define a specific number of images now
count = 1
while True:
print(f"Processing image {count}...")
# Find the image element by its ID
image_element = driver.find_element(By.ID, "cc-comic")
# Get the image URL from the 'src' attribute
image_url = image_element.get_attribute("src")
print(f"Found image URL: {image_url}")
# Download the image if it doesn't already exist
if download_image(image_url):
count += 1 # Increment count only if the image was downloaded
# Try to find the 'Next' button by its class
try:
print("Clicking the 'Next' button to load the next image...")
next_button = driver.find_element(By.CSS_SELECTOR, "a.cc-next")
# Navigate to the URL in the 'href' of the next button
next_button_url = next_button.get_attribute("href")
driver.get(next_button_url)
except:
# If the 'Next' button is not found, it means we've reached the last image
print("No 'Next' button found. Reached the end of images.")
break
# Close the browser
print("All images processed, closing the browser.")
driver.quit()

View File

@ -1,143 +1,142 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """Screen locker with workout verification for Arch Linux / i3wm
Screen locker with workout verification for Arch Linux / i3wm
Requires user to log their workout to unlock the screen. Requires user to log their workout to unlock the screen.
""" """
import tkinter as tk from datetime import datetime
import time
import sys
import re
import json import json
import os import os
from datetime import datetime import sys
import tkinter as tk
class ScreenLocker: class ScreenLocker:
def __init__(self, demo_mode=True): def __init__(self, demo_mode=True):
# Set up log file path # Set up log file path
script_dir = os.path.dirname(os.path.abspath(__file__)) script_dir = os.path.dirname(os.path.abspath(__file__))
self.log_file = os.path.join(script_dir, 'workout_log.json') self.log_file = os.path.join(script_dir, "workout_log.json")
# Check if already logged today # Check if already logged today
if self.has_logged_today(): if self.has_logged_today():
print("Workout already logged today. Skipping screen lock.") print("Workout already logged today. Skipping screen lock.")
sys.exit(0) sys.exit(0)
self.root = tk.Tk() self.root = tk.Tk()
self.root.title("Workout Locker" + (" [DEMO MODE]" if demo_mode else "")) self.root.title("Workout Locker" + (" [DEMO MODE]" if demo_mode else ""))
self.demo_mode = demo_mode self.demo_mode = demo_mode
self.lockout_time = 10 if demo_mode else 1800 # 10 seconds for demo, 30 minutes for production self.lockout_time = (
10 if demo_mode else 1800
) # 10 seconds for demo, 30 minutes for production
self.workout_data = {} self.workout_data = {}
# Get total screen dimensions across all monitors # Get total screen dimensions across all monitors
screen_width = self.root.winfo_screenwidth() screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight() screen_height = self.root.winfo_screenheight()
# Override redirect to bypass window manager (needed for multi-monitor spanning) # Override redirect to bypass window manager (needed for multi-monitor spanning)
self.root.overrideredirect(True) self.root.overrideredirect(True)
# Position window at 0,0 and span all monitors # Position window at 0,0 and span all monitors
self.root.geometry(f'{screen_width}x{screen_height}+0+0') self.root.geometry(f"{screen_width}x{screen_height}+0+0")
# Make window fullscreen and on top # Make window fullscreen and on top
self.root.attributes('-fullscreen', True) self.root.attributes("-fullscreen", True)
self.root.attributes('-topmost', True) self.root.attributes("-topmost", True)
self.root.configure(bg='#1a1a1a', cursor='arrow') self.root.configure(bg="#1a1a1a", cursor="arrow")
if demo_mode: if demo_mode:
# Demo mode: only close button allowed # Demo mode: only close button allowed
# Add close button in top-left corner # Add close button in top-left corner
close_btn = tk.Button( close_btn = tk.Button(
self.root, self.root,
text="✕ Close Demo", text="✕ Close Demo",
font=('Arial', 12), font=("Arial", 12),
bg='#ff4444', bg="#ff4444",
fg='white', fg="white",
command=self.close, command=self.close,
cursor='hand2' cursor="hand2",
) )
close_btn.place(x=10, y=10) close_btn.place(x=10, y=10)
# Create main container # Create main container
self.container = tk.Frame(self.root, bg='#1a1a1a') self.container = tk.Frame(self.root, bg="#1a1a1a")
self.container.place(relx=0.5, rely=0.5, anchor='center') self.container.place(relx=0.5, rely=0.5, anchor="center")
# Start with initial question # Start with initial question
self.ask_workout_done() self.ask_workout_done()
# Force window to update and grab input after everything is set up # Force window to update and grab input after everything is set up
self.root.update_idletasks() self.root.update_idletasks()
self.root.focus_force() self.root.focus_force()
self.root.grab_set_global() self.root.grab_set_global()
def clear_container(self): def clear_container(self):
for widget in self.container.winfo_children(): for widget in self.container.winfo_children():
widget.destroy() widget.destroy()
def ask_workout_done(self): def ask_workout_done(self):
self.clear_container() self.clear_container()
question = tk.Label( question = tk.Label(
self.container, self.container,
text="Did you work out today?", text="Did you work out today?",
font=('Arial', 36, 'bold'), font=("Arial", 36, "bold"),
fg='white', fg="white",
bg='#1a1a1a' bg="#1a1a1a",
) )
question.pack(pady=30) question.pack(pady=30)
button_frame = tk.Frame(self.container, bg='#1a1a1a') button_frame = tk.Frame(self.container, bg="#1a1a1a")
button_frame.pack(pady=20) button_frame.pack(pady=20)
yes_btn = tk.Button( yes_btn = tk.Button(
button_frame, button_frame,
text="YES", text="YES",
font=('Arial', 24, 'bold'), font=("Arial", 24, "bold"),
bg='#00aa00', bg="#00aa00",
fg='white', fg="white",
width=10, width=10,
command=self.ask_workout_type, command=self.ask_workout_type,
cursor='hand2' if self.demo_mode else '' cursor="hand2" if self.demo_mode else "",
) )
yes_btn.pack(side='left', padx=20) yes_btn.pack(side="left", padx=20)
no_btn = tk.Button( no_btn = tk.Button(
button_frame, button_frame,
text="NO", text="NO",
font=('Arial', 24, 'bold'), font=("Arial", 24, "bold"),
bg='#aa0000', bg="#aa0000",
fg='white', fg="white",
width=10, width=10,
command=self.lockout, command=self.lockout,
cursor='hand2' if self.demo_mode else '' cursor="hand2" if self.demo_mode else "",
) )
no_btn.pack(side='left', padx=20) no_btn.pack(side="left", padx=20)
def lockout(self): def lockout(self):
self.clear_container() self.clear_container()
self.lockout_label = tk.Label( self.lockout_label = tk.Label(
self.container, self.container,
text=f"Go work out!\nLocked for {self.lockout_time} seconds", text=f"Go work out!\nLocked for {self.lockout_time} seconds",
font=('Arial', 48, 'bold'), font=("Arial", 48, "bold"),
fg='#ff4444', fg="#ff4444",
bg='#1a1a1a' bg="#1a1a1a",
) )
self.lockout_label.pack(pady=30) self.lockout_label.pack(pady=30)
self.countdown_label = tk.Label( self.countdown_label = tk.Label(
self.container, self.container,
text=str(self.lockout_time), text=str(self.lockout_time),
font=('Arial', 120, 'bold'), font=("Arial", 120, "bold"),
fg='white', fg="white",
bg='#1a1a1a' bg="#1a1a1a",
) )
self.countdown_label.pack(pady=30) self.countdown_label.pack(pady=30)
self.remaining_time = self.lockout_time self.remaining_time = self.lockout_time
self.update_lockout_countdown() self.update_lockout_countdown()
def update_lockout_countdown(self): def update_lockout_countdown(self):
if self.remaining_time > 0: if self.remaining_time > 0:
self.countdown_label.config(text=str(self.remaining_time)) self.countdown_label.config(text=str(self.remaining_time))
@ -145,309 +144,365 @@ class ScreenLocker:
self.root.after(1000, self.update_lockout_countdown) self.root.after(1000, self.update_lockout_countdown)
else: else:
self.ask_workout_done() self.ask_workout_done()
def ask_workout_type(self): def ask_workout_type(self):
self.clear_container() self.clear_container()
question = tk.Label( question = tk.Label(
self.container, self.container,
text="What type of workout?", text="What type of workout?",
font=('Arial', 36, 'bold'), font=("Arial", 36, "bold"),
fg='white', fg="white",
bg='#1a1a1a' bg="#1a1a1a",
) )
question.pack(pady=30) question.pack(pady=30)
button_frame = tk.Frame(self.container, bg='#1a1a1a') button_frame = tk.Frame(self.container, bg="#1a1a1a")
button_frame.pack(pady=20) button_frame.pack(pady=20)
running_btn = tk.Button( running_btn = tk.Button(
button_frame, button_frame,
text="RUNNING", text="RUNNING",
font=('Arial', 24, 'bold'), font=("Arial", 24, "bold"),
bg='#0066cc', bg="#0066cc",
fg='white', fg="white",
width=15, width=15,
command=self.ask_running_details, command=self.ask_running_details,
cursor='hand2' if self.demo_mode else '' cursor="hand2" if self.demo_mode else "",
) )
running_btn.pack(side='left', padx=20) running_btn.pack(side="left", padx=20)
strength_btn = tk.Button( strength_btn = tk.Button(
button_frame, button_frame,
text="STRENGTH", text="STRENGTH",
font=('Arial', 24, 'bold'), font=("Arial", 24, "bold"),
bg='#cc6600', bg="#cc6600",
fg='white', fg="white",
width=15, width=15,
command=self.ask_strength_details, command=self.ask_strength_details,
cursor='hand2' if self.demo_mode else '' cursor="hand2" if self.demo_mode else "",
) )
strength_btn.pack(side='left', padx=20) strength_btn.pack(side="left", padx=20)
def ask_running_details(self): def ask_running_details(self):
self.clear_container() self.clear_container()
self.workout_data['type'] = 'running' self.workout_data["type"] = "running"
title = tk.Label( title = tk.Label(
self.container, self.container,
text="Running Details", text="Running Details",
font=('Arial', 36, 'bold'), font=("Arial", 36, "bold"),
fg='white', fg="white",
bg='#1a1a1a' bg="#1a1a1a",
) )
title.pack(pady=20) title.pack(pady=20)
# Distance # Distance
dist_frame = tk.Frame(self.container, bg='#1a1a1a') dist_frame = tk.Frame(self.container, bg="#1a1a1a")
dist_frame.pack(pady=10) dist_frame.pack(pady=10)
tk.Label(dist_frame, text="Distance (km):", font=('Arial', 20), fg='white', bg='#1a1a1a').pack(side='left', padx=10) tk.Label(
self.distance_entry = tk.Entry(dist_frame, font=('Arial', 20), width=10) dist_frame,
self.distance_entry.pack(side='left', padx=10) text="Distance (km):",
font=("Arial", 20),
fg="white",
bg="#1a1a1a",
).pack(side="left", padx=10)
self.distance_entry = tk.Entry(dist_frame, font=("Arial", 20), width=10)
self.distance_entry.pack(side="left", padx=10)
# Time # Time
time_frame = tk.Frame(self.container, bg='#1a1a1a') time_frame = tk.Frame(self.container, bg="#1a1a1a")
time_frame.pack(pady=10) time_frame.pack(pady=10)
tk.Label(time_frame, text="Time (minutes):", font=('Arial', 20), fg='white', bg='#1a1a1a').pack(side='left', padx=10) tk.Label(
self.time_entry = tk.Entry(time_frame, font=('Arial', 20), width=10) time_frame,
self.time_entry.pack(side='left', padx=10) text="Time (minutes):",
font=("Arial", 20),
fg="white",
bg="#1a1a1a",
).pack(side="left", padx=10)
self.time_entry = tk.Entry(time_frame, font=("Arial", 20), width=10)
self.time_entry.pack(side="left", padx=10)
# Pace # Pace
pace_frame = tk.Frame(self.container, bg='#1a1a1a') pace_frame = tk.Frame(self.container, bg="#1a1a1a")
pace_frame.pack(pady=10) pace_frame.pack(pady=10)
tk.Label(pace_frame, text="Pace (min/km):", font=('Arial', 20), fg='white', bg='#1a1a1a').pack(side='left', padx=10) tk.Label(
self.pace_entry = tk.Entry(pace_frame, font=('Arial', 20), width=10) pace_frame,
self.pace_entry.pack(side='left', padx=10) text="Pace (min/km):",
font=("Arial", 20),
fg="white",
bg="#1a1a1a",
).pack(side="left", padx=10)
self.pace_entry = tk.Entry(pace_frame, font=("Arial", 20), width=10)
self.pace_entry.pack(side="left", padx=10)
# Timer countdown label # Timer countdown label
self.timer_label = tk.Label( self.timer_label = tk.Label(
self.container, self.container, text="", font=("Arial", 16), fg="#ffaa00", bg="#1a1a1a"
text="",
font=('Arial', 16),
fg='#ffaa00',
bg='#1a1a1a'
) )
self.timer_label.pack(pady=10) self.timer_label.pack(pady=10)
self.submit_btn = tk.Button( self.submit_btn = tk.Button(
self.container, self.container,
text="SUBMIT (locked)", text="SUBMIT (locked)",
font=('Arial', 24, 'bold'), font=("Arial", 24, "bold"),
bg='#666666', bg="#666666",
fg='white', fg="white",
width=15, width=15,
state='disabled', state="disabled",
cursor='hand2' if self.demo_mode else '' cursor="hand2" if self.demo_mode else "",
) )
self.submit_btn.pack(pady=10) self.submit_btn.pack(pady=10)
# Back button # Back button
back_btn = tk.Button( back_btn = tk.Button(
self.container, self.container,
text="← BACK", text="← BACK",
font=('Arial', 18), font=("Arial", 18),
bg='#666666', bg="#666666",
fg='white', fg="white",
width=15, width=15,
command=self.ask_workout_type, command=self.ask_workout_type,
cursor='hand2' if self.demo_mode else '' cursor="hand2" if self.demo_mode else "",
) )
back_btn.pack(pady=10) back_btn.pack(pady=10)
# Start 30 second timer # Start 30 second timer
self.submit_unlock_time = 30 self.submit_unlock_time = 30
self.entries_to_check = [self.distance_entry, self.time_entry, self.pace_entry] self.entries_to_check = [self.distance_entry, self.time_entry, self.pace_entry]
self.submit_command = self.verify_running_data self.submit_command = self.verify_running_data
self.update_submit_timer() self.update_submit_timer()
def verify_running_data(self): def verify_running_data(self):
try: try:
distance = float(self.distance_entry.get()) distance = float(self.distance_entry.get())
time_mins = float(self.time_entry.get()) time_mins = float(self.time_entry.get())
pace = float(self.pace_entry.get()) pace = float(self.pace_entry.get())
# Sanity checks # Sanity checks
if distance <= 0 or distance > 100: if distance <= 0 or distance > 100:
self.show_error("Distance seems unrealistic (0-100 km)") self.show_error("Distance seems unrealistic (0-100 km)")
return return
if time_mins <= 0 or time_mins > 600: if time_mins <= 0 or time_mins > 600:
self.show_error("Time seems unrealistic (0-600 minutes)") self.show_error("Time seems unrealistic (0-600 minutes)")
return return
if pace <= 0 or pace > 20: if pace <= 0 or pace > 20:
self.show_error("Pace seems unrealistic (0-20 min/km)") self.show_error("Pace seems unrealistic (0-20 min/km)")
return return
# Calculate expected pace and check if close enough # Calculate expected pace and check if close enough
expected_pace = time_mins / distance expected_pace = time_mins / distance
pace_diff = abs(pace - expected_pace) pace_diff = abs(pace - expected_pace)
tolerance = expected_pace * 0.15 # 15% tolerance tolerance = expected_pace * 0.15 # 15% tolerance
if pace_diff > tolerance: if pace_diff > tolerance:
self.show_error(f"Pace doesn't match! Expected ~{expected_pace:.2f} min/km, got {pace:.2f}") self.show_error(
f"Pace doesn't match! Expected ~{expected_pace:.2f} min/km, got {pace:.2f}"
)
return return
# Data looks good # Data looks good
self.unlock_screen() self.unlock_screen()
except ValueError: except ValueError:
self.show_error("Please enter valid numbers") self.show_error("Please enter valid numbers")
def ask_strength_details(self): def ask_strength_details(self):
self.clear_container() self.clear_container()
self.workout_data['type'] = 'strength' self.workout_data["type"] = "strength"
title = tk.Label( title = tk.Label(
self.container, self.container,
text="Strength Training Details", text="Strength Training Details",
font=('Arial', 36, 'bold'), font=("Arial", 36, "bold"),
fg='white', fg="white",
bg='#1a1a1a' bg="#1a1a1a",
) )
title.pack(pady=20) title.pack(pady=20)
# Exercises # Exercises
ex_frame = tk.Frame(self.container, bg='#1a1a1a') ex_frame = tk.Frame(self.container, bg="#1a1a1a")
ex_frame.pack(pady=10) ex_frame.pack(pady=10)
tk.Label(ex_frame, text="Exercises (comma-separated):", font=('Arial', 18), fg='white', bg='#1a1a1a').pack(side='left', padx=10) tk.Label(
self.exercises_entry = tk.Entry(ex_frame, font=('Arial', 18), width=30) ex_frame,
self.exercises_entry.pack(side='left', padx=10) text="Exercises (comma-separated):",
font=("Arial", 18),
fg="white",
bg="#1a1a1a",
).pack(side="left", padx=10)
self.exercises_entry = tk.Entry(ex_frame, font=("Arial", 18), width=30)
self.exercises_entry.pack(side="left", padx=10)
# Sets per exercise # Sets per exercise
sets_frame = tk.Frame(self.container, bg='#1a1a1a') sets_frame = tk.Frame(self.container, bg="#1a1a1a")
sets_frame.pack(pady=10) sets_frame.pack(pady=10)
tk.Label(sets_frame, text="Sets per exercise (comma-separated):", font=('Arial', 18), fg='white', bg='#1a1a1a').pack(side='left', padx=10) tk.Label(
self.sets_entry = tk.Entry(sets_frame, font=('Arial', 18), width=20) sets_frame,
self.sets_entry.pack(side='left', padx=10) text="Sets per exercise (comma-separated):",
font=("Arial", 18),
fg="white",
bg="#1a1a1a",
).pack(side="left", padx=10)
self.sets_entry = tk.Entry(sets_frame, font=("Arial", 18), width=20)
self.sets_entry.pack(side="left", padx=10)
# Reps per set # Reps per set
reps_frame = tk.Frame(self.container, bg='#1a1a1a') reps_frame = tk.Frame(self.container, bg="#1a1a1a")
reps_frame.pack(pady=10) reps_frame.pack(pady=10)
tk.Label(reps_frame, text="Reps per set (comma-separated):", font=('Arial', 18), fg='white', bg='#1a1a1a').pack(side='left', padx=10) tk.Label(
self.reps_entry = tk.Entry(reps_frame, font=('Arial', 18), width=20) reps_frame,
self.reps_entry.pack(side='left', padx=10) text="Reps per set (comma-separated):",
font=("Arial", 18),
fg="white",
bg="#1a1a1a",
).pack(side="left", padx=10)
self.reps_entry = tk.Entry(reps_frame, font=("Arial", 18), width=20)
self.reps_entry.pack(side="left", padx=10)
# Weights # Weights
weights_frame = tk.Frame(self.container, bg='#1a1a1a') weights_frame = tk.Frame(self.container, bg="#1a1a1a")
weights_frame.pack(pady=10) weights_frame.pack(pady=10)
tk.Label(weights_frame, text="Weight per exercise in kg (comma-separated):", font=('Arial', 18), fg='white', bg='#1a1a1a').pack(side='left', padx=10) tk.Label(
self.weights_entry = tk.Entry(weights_frame, font=('Arial', 18), width=20) weights_frame,
self.weights_entry.pack(side='left', padx=10) text="Weight per exercise in kg (comma-separated):",
font=("Arial", 18),
fg="white",
bg="#1a1a1a",
).pack(side="left", padx=10)
self.weights_entry = tk.Entry(weights_frame, font=("Arial", 18), width=20)
self.weights_entry.pack(side="left", padx=10)
# Total weight lifted # Total weight lifted
total_frame = tk.Frame(self.container, bg='#1a1a1a') total_frame = tk.Frame(self.container, bg="#1a1a1a")
total_frame.pack(pady=10) total_frame.pack(pady=10)
tk.Label(total_frame, text="Total weight lifted (kg):", font=('Arial', 18), fg='white', bg='#1a1a1a').pack(side='left', padx=10) tk.Label(
self.total_weight_entry = tk.Entry(total_frame, font=('Arial', 18), width=15) total_frame,
self.total_weight_entry.pack(side='left', padx=10) text="Total weight lifted (kg):",
font=("Arial", 18),
fg="white",
bg="#1a1a1a",
).pack(side="left", padx=10)
self.total_weight_entry = tk.Entry(total_frame, font=("Arial", 18), width=15)
self.total_weight_entry.pack(side="left", padx=10)
# Timer countdown label # Timer countdown label
self.timer_label = tk.Label( self.timer_label = tk.Label(
self.container, self.container, text="", font=("Arial", 16), fg="#ffaa00", bg="#1a1a1a"
text="",
font=('Arial', 16),
fg='#ffaa00',
bg='#1a1a1a'
) )
self.timer_label.pack(pady=10) self.timer_label.pack(pady=10)
self.submit_btn = tk.Button( self.submit_btn = tk.Button(
self.container, self.container,
text="SUBMIT (locked)", text="SUBMIT (locked)",
font=('Arial', 24, 'bold'), font=("Arial", 24, "bold"),
bg='#666666', bg="#666666",
fg='white', fg="white",
width=15, width=15,
state='disabled', state="disabled",
cursor='hand2' if self.demo_mode else '' cursor="hand2" if self.demo_mode else "",
) )
self.submit_btn.pack(pady=10) self.submit_btn.pack(pady=10)
# Back button # Back button
back_btn = tk.Button( back_btn = tk.Button(
self.container, self.container,
text="← BACK", text="← BACK",
font=('Arial', 18), font=("Arial", 18),
bg='#666666', bg="#666666",
fg='white', fg="white",
width=15, width=15,
command=self.ask_workout_type, command=self.ask_workout_type,
cursor='hand2' if self.demo_mode else '' cursor="hand2" if self.demo_mode else "",
) )
back_btn.pack(pady=10) back_btn.pack(pady=10)
# Start 30 second timer # Start 30 second timer
self.submit_unlock_time = 30 self.submit_unlock_time = 30
self.entries_to_check = [self.exercises_entry, self.sets_entry, self.reps_entry, self.weights_entry, self.total_weight_entry] self.entries_to_check = [
self.exercises_entry,
self.sets_entry,
self.reps_entry,
self.weights_entry,
self.total_weight_entry,
]
self.submit_command = self.verify_strength_data self.submit_command = self.verify_strength_data
self.update_submit_timer() self.update_submit_timer()
def verify_strength_data(self): def verify_strength_data(self):
try: try:
exercises = [e.strip() for e in self.exercises_entry.get().split(',')] exercises = [e.strip() for e in self.exercises_entry.get().split(",")]
sets = [int(s.strip()) for s in self.sets_entry.get().split(',')] sets = [int(s.strip()) for s in self.sets_entry.get().split(",")]
reps = [int(r.strip()) for r in self.reps_entry.get().split(',')] reps = [int(r.strip()) for r in self.reps_entry.get().split(",")]
weights = [float(w.strip()) for w in self.weights_entry.get().split(',')] weights = [float(w.strip()) for w in self.weights_entry.get().split(",")]
total_weight = float(self.total_weight_entry.get()) total_weight = float(self.total_weight_entry.get())
# Check all lists have same length # Check all lists have same length
if not (len(exercises) == len(sets) == len(reps) == len(weights)): if not (len(exercises) == len(sets) == len(reps) == len(weights)):
self.show_error("Number of exercises, sets, reps, and weights must match") self.show_error(
"Number of exercises, sets, reps, and weights must match"
)
return return
# Check for empty or lazy entries # Check for empty or lazy entries
if any(len(ex) < 3 for ex in exercises): if any(len(ex) < 3 for ex in exercises):
self.show_error("Exercise names too short - be specific") self.show_error("Exercise names too short - be specific")
return return
# Sanity checks # Sanity checks
if any(s < 1 or s > 20 for s in sets): if any(s < 1 or s > 20 for s in sets):
self.show_error("Sets should be between 1-20") self.show_error("Sets should be between 1-20")
return return
if any(r < 1 or r > 100 for r in reps): if any(r < 1 or r > 100 for r in reps):
self.show_error("Reps should be between 1-100") self.show_error("Reps should be between 1-100")
return return
if any(w < 0 or w > 500 for w in weights): if any(w < 0 or w > 500 for w in weights):
self.show_error("Weights should be between 0-500 kg") self.show_error("Weights should be between 0-500 kg")
return return
# Calculate expected total weight # Calculate expected total weight
expected_total = sum(sets[i] * reps[i] * weights[i] for i in range(len(exercises))) expected_total = sum(
sets[i] * reps[i] * weights[i] for i in range(len(exercises))
)
weight_diff = abs(total_weight - expected_total) weight_diff = abs(total_weight - expected_total)
tolerance = expected_total * 0.15 # 15% tolerance tolerance = expected_total * 0.15 # 15% tolerance
if weight_diff > tolerance: if weight_diff > tolerance:
self.show_error(f"Total weight doesn't match! Expected ~{expected_total:.1f} kg, got {total_weight:.1f}") self.show_error(
f"Total weight doesn't match! Expected ~{expected_total:.1f} kg, got {total_weight:.1f}"
)
return return
# Data looks good # Data looks good
self.unlock_screen() self.unlock_screen()
except ValueError: except ValueError:
self.show_error("Please enter valid data in correct format") self.show_error("Please enter valid data in correct format")
def update_submit_timer(self): def update_submit_timer(self):
"""Update countdown timer and check if submit can be enabled""" """Update countdown timer and check if submit can be enabled"""
# Check if widgets still exist (user might have clicked back) # Check if widgets still exist (user might have clicked back)
try: try:
if self.submit_unlock_time > 0: if self.submit_unlock_time > 0:
self.timer_label.config(text=f"Submit available in {self.submit_unlock_time} seconds...") self.timer_label.config(
text=f"Submit available in {self.submit_unlock_time} seconds..."
)
self.submit_unlock_time -= 1 self.submit_unlock_time -= 1
self.root.after(1000, self.update_submit_timer) self.root.after(1000, self.update_submit_timer)
else: else:
# Timer finished, check if all entries have data # Timer finished, check if all entries have data
all_filled = all(entry.get().strip() for entry in self.entries_to_check) all_filled = all(entry.get().strip() for entry in self.entries_to_check)
if all_filled: if all_filled:
# Enable submit button # Enable submit button
self.submit_btn.config( self.submit_btn.config(
text="SUBMIT", text="SUBMIT",
state='normal', state="normal",
bg='#00aa00', bg="#00aa00",
command=self.submit_command command=self.submit_command,
) )
self.timer_label.config(text="You can now submit!") self.timer_label.config(text="You can now submit!")
else: else:
@ -457,18 +512,18 @@ class ScreenLocker:
except tk.TclError: except tk.TclError:
# Widgets were destroyed (user clicked back), stop the timer # Widgets were destroyed (user clicked back), stop the timer
pass pass
def check_entries_filled(self): def check_entries_filled(self):
"""Continuously check if entries are filled after timer expires""" """Continuously check if entries are filled after timer expires"""
try: try:
all_filled = all(entry.get().strip() for entry in self.entries_to_check) all_filled = all(entry.get().strip() for entry in self.entries_to_check)
if all_filled: if all_filled:
self.submit_btn.config( self.submit_btn.config(
text="SUBMIT", text="SUBMIT",
state='normal', state="normal",
bg='#00aa00', bg="#00aa00",
command=self.submit_command command=self.submit_command,
) )
self.timer_label.config(text="You can now submit!") self.timer_label.config(text="You can now submit!")
else: else:
@ -477,120 +532,120 @@ class ScreenLocker:
except tk.TclError: except tk.TclError:
# Widgets were destroyed (user clicked back), stop checking # Widgets were destroyed (user clicked back), stop checking
pass pass
def show_error(self, message): def show_error(self, message):
self.clear_container() self.clear_container()
error_label = tk.Label( error_label = tk.Label(
self.container, self.container,
text="ERROR", text="ERROR",
font=('Arial', 48, 'bold'), font=("Arial", 48, "bold"),
fg='#ff4444', fg="#ff4444",
bg='#1a1a1a' bg="#1a1a1a",
) )
error_label.pack(pady=20) error_label.pack(pady=20)
msg_label = tk.Label( msg_label = tk.Label(
self.container, self.container,
text=message, text=message,
font=('Arial', 24), font=("Arial", 24),
fg='white', fg="white",
bg='#1a1a1a', bg="#1a1a1a",
wraplength=800 wraplength=800,
) )
msg_label.pack(pady=20) msg_label.pack(pady=20)
retry_btn = tk.Button( retry_btn = tk.Button(
self.container, self.container,
text="TRY AGAIN", text="TRY AGAIN",
font=('Arial', 24, 'bold'), font=("Arial", 24, "bold"),
bg='#0066cc', bg="#0066cc",
fg='white', fg="white",
width=15, width=15,
command=self.ask_workout_done, command=self.ask_workout_done,
cursor='hand2' if self.demo_mode else '' cursor="hand2" if self.demo_mode else "",
) )
retry_btn.pack(pady=30) retry_btn.pack(pady=30)
def unlock_screen(self): def unlock_screen(self):
# Save workout data to log # Save workout data to log
self.save_workout_log() self.save_workout_log()
self.clear_container() self.clear_container()
success_label = tk.Label( success_label = tk.Label(
self.container, self.container,
text="Great job! 💪", text="Great job! 💪",
font=('Arial', 48, 'bold'), font=("Arial", 48, "bold"),
fg='#00ff00', fg="#00ff00",
bg='#1a1a1a' bg="#1a1a1a",
) )
success_label.pack(pady=30) success_label.pack(pady=30)
unlock_label = tk.Label( unlock_label = tk.Label(
self.container, self.container,
text="Screen Unlocked!", text="Screen Unlocked!",
font=('Arial', 36), font=("Arial", 36),
fg='white', fg="white",
bg='#1a1a1a' bg="#1a1a1a",
) )
unlock_label.pack(pady=20) unlock_label.pack(pady=20)
self.root.after(1500, self.close) self.root.after(1500, self.close)
def has_logged_today(self): def has_logged_today(self):
"""Check if workout has been logged today""" """Check if workout has been logged today"""
if not os.path.exists(self.log_file): if not os.path.exists(self.log_file):
return False return False
try: try:
with open(self.log_file, 'r') as f: with open(self.log_file) as f:
logs = json.load(f) logs = json.load(f)
today = datetime.now().strftime('%Y-%m-%d') today = datetime.now().strftime("%Y-%m-%d")
return today in logs return today in logs
except (json.JSONDecodeError, IOError): except (OSError, json.JSONDecodeError):
return False return False
def save_workout_log(self): def save_workout_log(self):
"""Save workout data to log file""" """Save workout data to log file"""
# Load existing logs # Load existing logs
logs = {} logs = {}
if os.path.exists(self.log_file): if os.path.exists(self.log_file):
try: try:
with open(self.log_file, 'r') as f: with open(self.log_file) as f:
logs = json.load(f) logs = json.load(f)
except (json.JSONDecodeError, IOError): except (OSError, json.JSONDecodeError):
logs = {} logs = {}
# Add today's workout # Add today's workout
today = datetime.now().strftime('%Y-%m-%d') today = datetime.now().strftime("%Y-%m-%d")
logs[today] = { logs[today] = {
'timestamp': datetime.now().isoformat(), "timestamp": datetime.now().isoformat(),
'workout_data': self.workout_data "workout_data": self.workout_data,
} }
# Save updated logs # Save updated logs
try: try:
with open(self.log_file, 'w') as f: with open(self.log_file, "w") as f:
json.dump(logs, f, indent=2) json.dump(logs, f, indent=2)
except IOError as e: except OSError as e:
print(f"Warning: Could not save workout log: {e}") print(f"Warning: Could not save workout log: {e}")
def close(self): def close(self):
self.root.destroy() self.root.destroy()
sys.exit(0) sys.exit(0)
def run(self): def run(self):
self.root.mainloop() self.root.mainloop()
if __name__ == '__main__': if __name__ == "__main__":
# Check for --production flag # Check for --production flag
demo_mode = True # Default to demo mode for safety demo_mode = True # Default to demo mode for safety
if len(sys.argv) > 1 and sys.argv[1] == '--production': if len(sys.argv) > 1 and sys.argv[1] == "--production":
demo_mode = False demo_mode = False
locker = ScreenLocker(demo_mode=demo_mode) locker = ScreenLocker(demo_mode=demo_mode)
locker.run() locker.run()

View File

@ -0,0 +1,20 @@
{
"2025-11-27": {
"timestamp": "2025-11-27T19:45:38.894904",
"workout_data": {
"type": "strength"
}
},
"2025-11-28": {
"timestamp": "2025-11-28T12:46:09.077724",
"workout_data": {
"type": "strength"
}
},
"2025-11-30": {
"timestamp": "2025-11-30T12:12:44.884093",
"workout_data": {
"type": "strength"
}
}
}

View File

@ -0,0 +1,57 @@
def calculate_symmetric_weights(N, middle_weight, factors=None):
"""Calculate symmetric weights for both even and odd N.
N: Number in which to split.
middle_weight: The middle value for symmetry.
factors: If provided, controls the difference in weights (used for the `split_x_into_n_symmetrically` function).
Must have length N // 2 or N // 2 - 1 depending on N.
"""
half_N = N // 2
weights_left = [middle_weight]
if factors:
for factor in factors:
next_weight = weights_left[-1] + factor
weights_left.append(next_weight)
else:
for i in range(half_N - 1):
weights_left.append(middle_weight - (i + 1))
if N % 2 == 0:
weights = weights_left[::-1] + weights_left
else:
weights = weights_left[::-1] + [middle_weight] + weights_left
return weights
def scale_to_total(X, weights):
"""Scale the weights so that their sum is proportional to X.
X: Total value to distribute.
weights: The list of weights to be scaled.
"""
total_weight = sum(weights)
base_unit = X / total_weight
distances = [base_unit * weight for weight in weights]
return distances
def split_x_into_n_symmetrically(X, N, factors):
"""X: Total value to distribute.
N: Number in which we split.
factors: List controlling the difference in weights between consecutive days.
Must have length of N // 2 (if N is odd) or (N // 2 - 1) (if N is even).
"""
weights = calculate_symmetric_weights(N, middle_weight=1, factors=factors)
return scale_to_total(X, weights)
def split_x_into_n_middle(X, N, middle_value):
"""X: Total value to distribute.
N: Number in which we split.
middle_value: Value of the middle number (the biggest weight).
"""
weights = calculate_symmetric_weights(N, middle_weight=middle_value)
return scale_to_total(X, weights)

View File

@ -21,9 +21,11 @@ python3 PYTHON/analyze_chess_game.py lichess_bot_game_8GSdY3Ci.log
``` ```
Options: Options:
- `--engine /path/to/stockfish` to specify a custom engine path - `--engine /path/to/stockfish` to specify a custom engine path
- `--time 0.2` seconds per evaluation (default) - `--time 0.2` seconds per evaluation (default)
- `--depth 12` fixed depth instead of time - `--depth 12` fixed depth instead of time
The script prints a table with, for each ply: The script prints a table with, for each ply:
- side to move, SAN move, eval before/after from mover's POV, delta, classification, and Stockfish best move suggestion. - side to move, SAN move, eval before/after from mover's POV, delta, classification, and Stockfish best move suggestion.

217
PYTHON/stockfish_analysis/analyze_chess_game.py Normal file → Executable file
View File

@ -1,6 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """Analyze a chess game's moves using a local Stockfish engine and rate each move.
Analyze a chess game's moves using a local Stockfish engine and rate each move.
Usage: Usage:
python3 PYTHON/analyze_chess_game.py <path-to-file> python3 PYTHON/analyze_chess_game.py <path-to-file>
@ -22,11 +21,10 @@ from __future__ import annotations
import argparse import argparse
import io import io
import multiprocessing
import os import os
import re import re
import sys import sys
from typing import Optional, Tuple
import multiprocessing
try: try:
import psutil # type: ignore import psutil # type: ignore
@ -37,13 +35,15 @@ try:
import chess import chess
import chess.engine import chess.engine
import chess.pgn import chess.pgn
except Exception as e: # pragma: no cover except Exception: # pragma: no cover
print("Missing dependency. Please install python-chess:", file=sys.stderr) print("Missing dependency. Please install python-chess:", file=sys.stderr)
print(" pip install -r PYTHON/stockfish_analysis/requirements.txt", file=sys.stderr) print(
" pip install -r PYTHON/stockfish_analysis/requirements.txt", file=sys.stderr
)
raise raise
def extract_pgn_text(raw: str) -> Optional[str]: def extract_pgn_text(raw: str) -> str | None:
"""Try to extract a PGN block from a possibly noisy file. """Try to extract a PGN block from a possibly noisy file.
Strategies tried in order: Strategies tried in order:
@ -79,7 +79,9 @@ def extract_pgn_text(raw: str) -> Optional[str]:
return None return None
def score_to_cp(score: chess.engine.PovScore, pov_white: bool) -> Tuple[Optional[int], Optional[int]]: def score_to_cp(
score: chess.engine.PovScore, pov_white: bool
) -> tuple[int | None, int | None]:
"""Return tuple (cp, mate_in) from a PovScore for the given POV color. """Return tuple (cp, mate_in) from a PovScore for the given POV color.
If it's a mate score, cp will be None and mate_in will be +/-N (positive means mate for POV side). If it's a mate score, cp will be None and mate_in will be +/-N (positive means mate for POV side).
@ -93,7 +95,7 @@ def score_to_cp(score: chess.engine.PovScore, pov_white: bool) -> Tuple[Optional
return s.score(mate_score=None), None return s.score(mate_score=None), None
def classify_cp_loss(cp_loss: Optional[int]) -> str: def classify_cp_loss(cp_loss: int | None) -> str:
"""Classify move quality using Lichess-like centipawn loss bands. """Classify move quality using Lichess-like centipawn loss bands.
Loss is best_eval(cp) - played_eval(cp), from the mover's POV (positive is worse). Loss is best_eval(cp) - played_eval(cp), from the mover's POV (positive is worse).
@ -120,7 +122,7 @@ def classify_cp_loss(cp_loss: Optional[int]) -> str:
return "Blunder" return "Blunder"
def fmt_eval(cp: Optional[int], mate_in: Optional[int]) -> str: def fmt_eval(cp: int | None, mate_in: int | None) -> str:
if mate_in is not None: if mate_in is not None:
sign = "+" if mate_in > 0 else "" sign = "+" if mate_in > 0 else ""
return f"M{sign}{mate_in}" return f"M{sign}{mate_in}"
@ -130,7 +132,7 @@ def fmt_eval(cp: Optional[int], mate_in: Optional[int]) -> str:
return f"{cp/100.0:+.2f}" return f"{cp/100.0:+.2f}"
def _parse_threads(value: str) -> Optional[int]: def _parse_threads(value: str) -> int | None:
v = value.strip().lower() v = value.strip().lower()
if v in ("auto", "max", ""): # auto-detect if v in ("auto", "max", ""): # auto-detect
return None return None
@ -141,7 +143,7 @@ def _parse_threads(value: str) -> Optional[int]:
raise argparse.ArgumentTypeError("--threads must be an integer or 'auto'") raise argparse.ArgumentTypeError("--threads must be an integer or 'auto'")
def _parse_hash_mb(value: str) -> Optional[int]: def _parse_hash_mb(value: str) -> int | None:
v = value.strip().lower() v = value.strip().lower()
if v in ("auto", "max", ""): # auto-detect if v in ("auto", "max", ""): # auto-detect
return None return None
@ -152,7 +154,7 @@ def _parse_hash_mb(value: str) -> Optional[int]:
raise argparse.ArgumentTypeError("--hash-mb must be an integer (MB) or 'auto'") raise argparse.ArgumentTypeError("--hash-mb must be an integer (MB) or 'auto'")
def _detect_total_mem_mb() -> Optional[int]: def _detect_total_mem_mb() -> int | None:
# Prefer psutil if available # Prefer psutil if available
if psutil is not None: if psutil is not None:
try: try:
@ -161,7 +163,7 @@ def _detect_total_mem_mb() -> Optional[int]:
pass pass
# Fallback: Linux /proc/meminfo # Fallback: Linux /proc/meminfo
try: try:
with open("/proc/meminfo", "r", encoding="utf-8", errors="ignore") as f: with open("/proc/meminfo", encoding="utf-8", errors="ignore") as f:
for line in f: for line in f:
if line.startswith("MemTotal:"): if line.startswith("MemTotal:"):
parts = line.split() parts = line.split()
@ -183,7 +185,7 @@ def _auto_hash_mb(threads_wanted: int, engine_options) -> int:
opt = engine_options.get("Hash") opt = engine_options.get("Hash")
max_allowed = None max_allowed = None
try: try:
max_allowed = getattr(opt, "max") if opt is not None else None max_allowed = opt.max if opt is not None else None
except Exception: except Exception:
max_allowed = None max_allowed = None
if isinstance(max_allowed, int): if isinstance(max_allowed, int):
@ -195,27 +197,61 @@ def _auto_hash_mb(threads_wanted: int, engine_options) -> int:
def main(): def main():
ap = argparse.ArgumentParser(description="Analyze a chess game's moves with Stockfish and rate each move.") ap = argparse.ArgumentParser(
description="Analyze a chess game's moves with Stockfish and rate each move."
)
ap.add_argument("file", help="Path to a PGN file or a log containing a PGN section") ap.add_argument("file", help="Path to a PGN file or a log containing a PGN section")
ap.add_argument("--engine", default="stockfish", help="Path to stockfish executable (default: stockfish)") ap.add_argument(
"--engine",
default="stockfish",
help="Path to stockfish executable (default: stockfish)",
)
# Exactly one of time or depth may be provided; default to time # Exactly one of time or depth may be provided; default to time
ap.add_argument("--time", type=float, default=0.5, help="Analysis time per evaluation in seconds (default: 0.5)") ap.add_argument(
ap.add_argument("--depth", type=int, default=None, help="Fixed depth per evaluation (overrides --time)") "--time",
type=float,
default=0.5,
help="Analysis time per evaluation in seconds (default: 0.5)",
)
ap.add_argument(
"--depth",
type=int,
default=None,
help="Fixed depth per evaluation (overrides --time)",
)
# Performance knobs # Performance knobs
ap.add_argument("--threads", type=_parse_threads, default=None, metavar="auto|N", ap.add_argument(
help="Engine threads to use (default: auto = all logical cores)") "--threads",
ap.add_argument("--hash-mb", type=_parse_hash_mb, default=None, metavar="auto|MB", type=_parse_threads,
help="Hash table size in MB (default: auto = up to half RAM, capped)") default=None,
ap.add_argument("--multipv", type=int, default=2, help="Number of principal variations to compute (default: 1)") metavar="auto|N",
ap.add_argument("--last-move-only", action="store_true", help="Engine threads to use (default: auto = all logical cores)",
help="Analyze only the last move of the main line (reports its eval and the best move)") )
ap.add_argument(
"--hash-mb",
type=_parse_hash_mb,
default=None,
metavar="auto|MB",
help="Hash table size in MB (default: auto = up to half RAM, capped)",
)
ap.add_argument(
"--multipv",
type=int,
default=2,
help="Number of principal variations to compute (default: 1)",
)
ap.add_argument(
"--last-move-only",
action="store_true",
help="Analyze only the last move of the main line (reports its eval and the best move)",
)
args = ap.parse_args() args = ap.parse_args()
if not os.path.isfile(args.file): if not os.path.isfile(args.file):
print(f"Input not found: {args.file}", file=sys.stderr) print(f"Input not found: {args.file}", file=sys.stderr)
sys.exit(1) sys.exit(1)
with open(args.file, "r", encoding="utf-8", errors="replace") as f: with open(args.file, encoding="utf-8", errors="replace") as f:
raw = f.read() raw = f.read()
pgn_text = extract_pgn_text(raw) pgn_text = extract_pgn_text(raw)
@ -233,7 +269,10 @@ def main():
engine = chess.engine.SimpleEngine.popen_uci([args.engine]) engine = chess.engine.SimpleEngine.popen_uci([args.engine])
except FileNotFoundError: except FileNotFoundError:
print(f"Could not launch engine at: {args.engine}", file=sys.stderr) print(f"Could not launch engine at: {args.engine}", file=sys.stderr)
print("Ensure Stockfish is installed and in PATH, or specify with --engine.", file=sys.stderr) print(
"Ensure Stockfish is installed and in PATH, or specify with --engine.",
file=sys.stderr,
)
sys.exit(4) sys.exit(4)
# Configure engine performance options if available # Configure engine performance options if available
@ -243,7 +282,9 @@ def main():
options = {} options = {}
# Threads # Threads
wanted_threads = args.threads if args.threads is not None else (multiprocessing.cpu_count() or 1) wanted_threads = (
args.threads if args.threads is not None else (multiprocessing.cpu_count() or 1)
)
# Respect engine bounds if present # Respect engine bounds if present
if "Threads" in options: if "Threads" in options:
try: try:
@ -307,18 +348,26 @@ def main():
result = game.headers.get("Result", "*") result = game.headers.get("Result", "*")
print(f" {white} vs {black} Result: {result}") print(f" {white} vs {black} Result: {result}")
print() print()
print("Columns: ply side move played_eval best_eval loss class best_suggestion") print(
"Columns: ply side move played_eval best_eval loss class best_suggestion"
)
# Brief performance summary (best-effort) # Brief performance summary (best-effort)
try: try:
thr_show = int(wanted_threads) thr_show = int(wanted_threads)
except Exception: except Exception:
thr_show = 1 thr_show = 1
try: try:
hash_show = int(engine.options.get("Hash").value) if hasattr(engine, "options") and engine.options.get("Hash") else None hash_show = (
int(engine.options.get("Hash").value)
if hasattr(engine, "options") and engine.options.get("Hash")
else None
)
except Exception: except Exception:
hash_show = None hash_show = None
if hash_show is not None: if hash_show is not None:
print(f"Using engine options: Threads={thr_show}, Hash={hash_show} MB, MultiPV={effective_mpv}") print(
f"Using engine options: Threads={thr_show}, Hash={hash_show} MB, MultiPV={effective_mpv}"
)
else: else:
print(f"Using engine options: Threads={thr_show}, MultiPV={effective_mpv}") print(f"Using engine options: Threads={thr_show}, MultiPV={effective_mpv}")
@ -339,10 +388,20 @@ def main():
# If this is the final move in the mainline, analyze it and stop. # If this is the final move in the mainline, analyze it and stop.
if not move_node.variations: if not move_node.variations:
# Analyse current position to get engine best move suggestion # Analyse current position to get engine best move suggestion
info_root_raw = engine.analyse(board, limit=limit, multipv=effective_mpv) info_root_raw = engine.analyse(
info_root = info_root_raw[0] if isinstance(info_root_raw, list) else info_root_raw board, limit=limit, multipv=effective_mpv
)
info_root = (
info_root_raw[0]
if isinstance(info_root_raw, list)
else info_root_raw
)
best_move = None best_move = None
if info_root is not None and "pv" in info_root and info_root["pv"]: if (
info_root is not None
and "pv" in info_root
and info_root["pv"]
):
best_move = info_root["pv"][0] best_move = info_root["pv"][0]
if best_move is None: if best_move is None:
res = engine.play(board, limit) res = engine.play(board, limit)
@ -353,29 +412,47 @@ def main():
# Evaluate played move # Evaluate played move
board_played = board.copy() board_played = board.copy()
board_played.push(move) board_played.push(move)
info_played_raw = engine.analyse(board_played, limit=limit, multipv=effective_mpv) info_played_raw = engine.analyse(
info_played = info_played_raw[0] if isinstance(info_played_raw, list) else info_played_raw board_played, limit=limit, multipv=effective_mpv
)
info_played = (
info_played_raw[0]
if isinstance(info_played_raw, list)
else info_played_raw
)
if info_played is None or "score" not in info_played: if info_played is None or "score" not in info_played:
played_cp, played_mate = None, None played_cp, played_mate = None, None
else: else:
played_cp, played_mate = score_to_cp(info_played["score"], pov_white=mover_white) played_cp, played_mate = score_to_cp(
info_played["score"], pov_white=mover_white
)
# Evaluate best move position (for mover POV) # Evaluate best move position (for mover POV)
best_san = board.san(best_move) if best_move is not None else "?" best_san = (
board.san(best_move) if best_move is not None else "?"
)
if best_move is not None: if best_move is not None:
board_best = board.copy() board_best = board.copy()
board_best.push(best_move) board_best.push(best_move)
info_best_raw = engine.analyse(board_best, limit=limit, multipv=effective_mpv) info_best_raw = engine.analyse(
info_best = info_best_raw[0] if isinstance(info_best_raw, list) else info_best_raw board_best, limit=limit, multipv=effective_mpv
)
info_best = (
info_best_raw[0]
if isinstance(info_best_raw, list)
else info_best_raw
)
if info_best is None or "score" not in info_best: if info_best is None or "score" not in info_best:
best_cp, best_mate = None, None best_cp, best_mate = None, None
else: else:
best_cp, best_mate = score_to_cp(info_best["score"], pov_white=mover_white) best_cp, best_mate = score_to_cp(
info_best["score"], pov_white=mover_white
)
else: else:
best_cp, best_mate = None, None best_cp, best_mate = None, None
# Compute loss/classification # Compute loss/classification
cp_loss: Optional[int] = None cp_loss: int | None = None
classification = "Unknown" classification = "Unknown"
if best_mate is not None or played_mate is not None: if best_mate is not None or played_mate is not None:
if best_mate is not None and played_mate is not None: if best_mate is not None and played_mate is not None:
@ -397,10 +474,9 @@ def main():
classification = "Blunder" classification = "Blunder"
else: else:
classification = "Blunder" classification = "Blunder"
else: elif best_cp is not None and played_cp is not None:
if best_cp is not None and played_cp is not None: cp_loss = max(0, best_cp - played_cp)
cp_loss = max(0, best_cp - played_cp) classification = classify_cp_loss(cp_loss)
classification = classify_cp_loss(cp_loss)
side = "W" if mover_white else "B" side = "W" if mover_white else "B"
print( print(
@ -422,8 +498,14 @@ def main():
mover_white = board.turn mover_white = board.turn
# Analyse position to get engine best move suggestion # Analyse position to get engine best move suggestion
info_root_raw = engine.analyse(board, limit=limit, multipv=effective_mpv) info_root_raw = engine.analyse(
info_root = info_root_raw[0] if isinstance(info_root_raw, list) else info_root_raw board, limit=limit, multipv=effective_mpv
)
info_root = (
info_root_raw[0]
if isinstance(info_root_raw, list)
else info_root_raw
)
best_move = None best_move = None
if info_root is not None and "pv" in info_root and info_root["pv"]: if info_root is not None and "pv" in info_root and info_root["pv"]:
best_move = info_root["pv"][0] best_move = info_root["pv"][0]
@ -436,29 +518,45 @@ def main():
san = board.san(move) san = board.san(move)
board_played = board.copy() board_played = board.copy()
board_played.push(move) board_played.push(move)
info_played_raw = engine.analyse(board_played, limit=limit, multipv=effective_mpv) info_played_raw = engine.analyse(
info_played = info_played_raw[0] if isinstance(info_played_raw, list) else info_played_raw board_played, limit=limit, multipv=effective_mpv
)
info_played = (
info_played_raw[0]
if isinstance(info_played_raw, list)
else info_played_raw
)
if info_played is None or "score" not in info_played: if info_played is None or "score" not in info_played:
played_cp, played_mate = None, None played_cp, played_mate = None, None
else: else:
played_cp, played_mate = score_to_cp(info_played["score"], pov_white=mover_white) played_cp, played_mate = score_to_cp(
info_played["score"], pov_white=mover_white
)
# Evaluate best move position (for mover POV) # Evaluate best move position (for mover POV)
best_san = board.san(best_move) if best_move is not None else "?" best_san = board.san(best_move) if best_move is not None else "?"
if best_move is not None: if best_move is not None:
board_best = board.copy() board_best = board.copy()
board_best.push(best_move) board_best.push(best_move)
info_best_raw = engine.analyse(board_best, limit=limit, multipv=effective_mpv) info_best_raw = engine.analyse(
info_best = info_best_raw[0] if isinstance(info_best_raw, list) else info_best_raw board_best, limit=limit, multipv=effective_mpv
)
info_best = (
info_best_raw[0]
if isinstance(info_best_raw, list)
else info_best_raw
)
if info_best is None or "score" not in info_best: if info_best is None or "score" not in info_best:
best_cp, best_mate = None, None best_cp, best_mate = None, None
else: else:
best_cp, best_mate = score_to_cp(info_best["score"], pov_white=mover_white) best_cp, best_mate = score_to_cp(
info_best["score"], pov_white=mover_white
)
else: else:
best_cp, best_mate = None, None best_cp, best_mate = None, None
# Compute centipawn loss bands # Compute centipawn loss bands
cp_loss: Optional[int] = None cp_loss: int | None = None
classification = "Unknown" classification = "Unknown"
# Handle mate cases first # Handle mate cases first
if best_mate is not None or played_mate is not None: if best_mate is not None or played_mate is not None:
@ -486,10 +584,9 @@ def main():
else: else:
# Losing a forced mate or missing one # Losing a forced mate or missing one
classification = "Blunder" classification = "Blunder"
else: elif best_cp is not None and played_cp is not None:
if best_cp is not None and played_cp is not None: cp_loss = max(0, best_cp - played_cp)
cp_loss = max(0, best_cp - played_cp) classification = classify_cp_loss(cp_loss)
classification = classify_cp_loss(cp_loss)
side = "W" if mover_white else "B" side = "W" if mover_white else "B"
print( print(

View File

@ -1,2 +1,2 @@
psutil>=5.9
python-chess>=1.999 python-chess>=1.999
psutil>=5.9

View File

@ -0,0 +1,15 @@
# tagDivider
Python script creating two directories, showing images in the script directory and putting those images into one of those directories depending on user input
How to use:
1. Install opencv for python3
a) Linux: sudo apt-get install python3-opencv
2. Put the script into whatever folder you have images in
3. Run the script
python3 tagDivider.py
4. Enter folders names in terminal
5. Click "a" or "d" accordingly to folder you want image to be in
If you want to change default buttons just modify script

View File

@ -0,0 +1,72 @@
import os # for: os.getcwd; os.mkdir; os.listdir;
from os import path # for: os.path.abspath
import shutil # for: shutil.move
import cv2 # for: cv2.imread; cv2.namedWindow; cv2.imshow; cv2.waitKey; cv2.destroyAllWindows; cv2.IMREAD_COLOR
IMAGE_EXTENSION = (
".bmp",
".dib",
".jpeg",
".jpg",
".jpe",
".jp2",
".png",
".pbm",
".pgm",
".ppm",
".pxm",
".pnm",
".pfm",
".sr",
".ras",
".tiff",
".tif",
".exr",
".hdr",
".pic",
) # Stolen from here: https://docs.opencv.org/4.5.2/d4/da8/group__imgcodecs.html I didn't include .webp because if the image is animated shit does not work
LEFT_FOLDER_CODE = 100 # Default 100 - 'd'
RIGHT_FOLDER_CODE = 97 # Default 97 - 'a'
# Change by checking: https://www.ascii-code.com/
firstFolderName = input("Enter first folder name: [a] ")
secondFolderName = input("Enter second folder name: [d] ")
currentPath = os.path.abspath(
os.getcwd()
) # Stolen from: https://stackoverflow.com/q/3430372
os.chdir(currentPath) # Change working directory to the path where the python file is
if (
path.isdir(firstFolderName) != 1
): # Check if folder already exists, if it does not make it
os.mkdir(firstFolderName)
if path.isdir(secondFolderName) != 1:
os.mkdir(secondFolderName)
for filename in os.listdir(
os.getcwd()
): # Go through every file in the working directory
if (filename.lower()).endswith(
IMAGE_EXTENSION
): # If the file name ends with image extension
print(filename)
image = cv2.imread(filename, cv2.IMREAD_COLOR)
window_name = filename.split(".")[0]
cv2.namedWindow(window_name) # Window name is the same as image file name
cv2.imshow(window_name, image)
key = cv2.waitKey()
if key == RIGHT_FOLDER_CODE:
shutil.move(
currentPath + "/" + filename,
currentPath + "/" + firstFolderName + "/" + filename,
)
elif key == LEFT_FOLDER_CODE:
shutil.move(
currentPath + "/" + filename,
currentPath + "/" + secondFolderName + "/" + filename,
)
# else:
# print(key)
cv2.destroyAllWindows()

View File

@ -15,5 +15,6 @@ npm run dev
Then open the printed local URL (default http://localhost:5173). Then open the printed local URL (default http://localhost:5173).
Notes: Notes:
- The Battery Status API may be unavailable or disabled in some browsers for privacy reasons. In that case, the app will show a helpful message. - The Battery Status API may be unavailable or disabled in some browsers for privacy reasons. In that case, the app will show a helpful message.
- On laptops it typically works in Chromium-based browsers; mobile support varies. - On laptops it typically works in Chromium-based browsers; mobile support varies.

View File

@ -4,9 +4,9 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"build": "vite build", "build": "vite build",
"preview": "vite preview --strictPort --port 5173" "preview": "vite preview --strictPort --port 5173"
}, },
"dependencies": { "dependencies": {
@ -14,7 +14,7 @@
"react-dom": "^18.3.1" "react-dom": "^18.3.1"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",
"@types/react": "^18.3.5", "@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"typescript": "^5.5.4", "typescript": "^5.5.4",

View File

@ -1,12 +1,13 @@
# Champions League Live Scores (React + TS) # Champions League Live Scores (React + TS)
This app displays live and today's UEFA Champions League results. It uses: This app displays live and today's UEFA Champions League results. It uses:
- React + TypeScript (Vite) for the frontend - React + TypeScript (Vite) for the frontend
- A tiny Express proxy server that calls football-data.org to fetch match data - A tiny Express proxy server that calls football-data.org to fetch match data
## Setup ## Setup
1) Create a `.env` file in `TS/champions_leauge_scores/`: 1. Create a `.env` file in `TS/champions_leauge_scores/`:
``` ```
FOOTBALL_DATA_API_KEY=your_api_token_here FOOTBALL_DATA_API_KEY=your_api_token_here
@ -15,7 +16,7 @@ PORT=8787
Sign up at https://www.football-data.org/ to get a free API token. Free tier has rate limits. Sign up at https://www.football-data.org/ to get a free API token. Free tier has rate limits.
2) Install dependencies and run both servers: 2. Install dependencies and run both servers:
``` ```
npm install npm install
@ -26,9 +27,11 @@ npm run dev
- API Proxy: http://localhost:8787 - API Proxy: http://localhost:8787
## Notes ## Notes
- Live endpoint: `GET /api/live` - Live endpoint: `GET /api/live`
- Today endpoint: `GET /api/matches` (uses today's date by default) - Today endpoint: `GET /api/matches` (uses today's date by default)
- Edit polling intervals in `src/App.tsx` if needed. - Edit polling intervals in `src/App.tsx` if needed.
## License ## License
MIT
MIT

View File

@ -4,29 +4,29 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "concurrently \"vite\" \"npm:server:dev\"", "dev": "concurrently \"vite\" \"npm:server:dev\"",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"server:dev": "tsx watch server/src/server.ts" "server:dev": "tsx watch server/src/server.ts"
}, },
"dependencies": { "dependencies": {
"axios": "^1.7.2", "axios": "^1.7.2",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^4.19.2", "express": "^4.19.2",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1" "react-dom": "^18.3.1"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/node": "^20.12.12", "@types/node": "^20.12.12",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"ts-node-dev": "^2.0.0", "ts-node-dev": "^2.0.0",
"tsx": "^4.19.2", "tsx": "^4.19.2",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"vite": "^5.3.3" "vite": "^5.3.3"
} }

View File

@ -0,0 +1,92 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"two-inputs": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/two-inputs",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"styles": [
"@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.scss"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "two-inputs:build:production"
},
"development": {
"buildTarget": "two-inputs:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "two-inputs:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"styles": [
"@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.scss"
],
"scripts": []
}
}
}
}
}
}

View File

@ -0,0 +1,40 @@
{
"name": "two-inputs",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^17.0.0",
"@angular/cdk": "^17.3.5",
"@angular/common": "^17.0.0",
"@angular/compiler": "^17.0.0",
"@angular/core": "^17.0.0",
"@angular/forms": "^17.0.0",
"@angular/material": "^17.3.5",
"@angular/platform-browser": "^17.0.0",
"@angular/platform-browser-dynamic": "^17.0.0",
"@angular/router": "^17.0.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.0.3",
"@angular/cli": "^17.0.3",
"@angular/compiler-cli": "^17.0.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.1.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.2.2"
}
}

View File

@ -0,0 +1,30 @@
<mat-form-field class="example-form-field">
<mat-label>min</mat-label>
<input matInput type="number" [(ngModel)]="min" (input)="updateInput()">
</mat-form-field>
<mat-form-field class="example-form-field">
<mat-label>max</mat-label>
<input matInput type="number" [(ngModel)]="max" (input)="updateInput()">
</mat-form-field>
<mat-form-field class="example-form-field">
<mat-label>step</mat-label>
<input matInput type="number" [(ngModel)]="step" (input)="updateInput()">
</mat-form-field>
<mat-form-field class="example-form-field">
<mat-label>targetValue</mat-label>
<input matInput type="number" [(ngModel)]="targetValue" (input)="updateInput()">
</mat-form-field> <br>
<mat-form-field class="example-form-field">
<mat-label>inputOne</mat-label>
<input disabled="true" matInput type="number" [min]="min" [step]="step" [max]="max" [(ngModel)]="inputOne">
</mat-form-field>
<button mat-button (click)="upOne()"> UP </button>
<button mat-button (click)="downOne()"> DOWN </button>
<br>
<mat-form-field class="example-form-field">
<mat-label>inputTwo</mat-label>
<input disabled="true" matInput type="number" [min]="min" [step]="step" [max]="max" [(ngModel)]="inputTwo">
</mat-form-field>
<button mat-button (click)="upTwo()"> UP </button>
<button mat-button (click)="downTwo()"> DOWN </button>
<br>

Some files were not shown because too many files have changed in this diff Show More