mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 15:23:03 +02:00
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:
parent
5a6095bd8f
commit
e3f9e6dc0b
18
.github/workflows/python-tests.yml
vendored
18
.github/workflows/python-tests.yml
vendored
@ -4,17 +4,17 @@ 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
|
||||||
|
|||||||
268
.pre-commit-config.yaml
Normal file
268
.pre-commit-config.yaml
Normal 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]
|
||||||
4
.vscode/tasks.json
vendored
4
.vscode/tasks.json
vendored
@ -5,9 +5,7 @@
|
|||||||
"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"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
1
Bash/ffmpeg-build/FFmpeg
Submodule
1
Bash/ffmpeg-build/FFmpeg
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 0bc54cddb1050c3c55bc65adbd3c8aa90d7eb457
|
||||||
@ -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
|
||||||
|
|||||||
@ -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).
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
# Lint script for imageViewer project
|
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Lint script for imageViewer project
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
|||||||
4
C/misc/randomJPG/.gitignore
vendored
Normal file
4
C/misc/randomJPG/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
*.jpeg
|
||||||
|
*.jpg
|
||||||
|
generated_images*/*
|
||||||
|
generate_images
|
||||||
6
C/misc/randomJPG/Readme.md
Normal file
6
C/misc/randomJPG/Readme.md
Normal 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
1
C/misc/split/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
split
|
||||||
@ -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
65
C/scrapeWebsite/.gitignore
vendored
Normal 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
|
||||||
5
CPP/SFMLEngine/readme.md
Normal file
5
CPP/SFMLEngine/readme.md
Normal 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
|
||||||
25
CPP/miscelanious/Pi/main.cpp
Normal file
25
CPP/miscelanious/Pi/main.cpp
Normal 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;
|
||||||
|
}
|
||||||
410
CPP/miscelanious/brydz/brydz.cpp
Normal file
410
CPP/miscelanious/brydz/brydz.cpp
Normal 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;
|
||||||
|
}
|
||||||
91
CPP/miscelanious/calculateShotsDarts/basic.cpp
Normal file
91
CPP/miscelanious/calculateShotsDarts/basic.cpp
Normal 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
|
||||||
89
CPP/miscelanious/calculateShotsDarts/main.cpp
Normal file
89
CPP/miscelanious/calculateShotsDarts/main.cpp
Normal 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
|
||||||
60
CPP/miscelanious/findIntegerPercentageValue/main.cpp
Normal file
60
CPP/miscelanious/findIntegerPercentageValue/main.cpp
Normal 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;
|
||||||
|
}
|
||||||
133
CPP/miscelanious/howManyValidISBNNumbersAreThere/11.cpp
Normal file
133
CPP/miscelanious/howManyValidISBNNumbersAreThere/11.cpp
Normal 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
|
||||||
48
CPP/miscelanious/howOftenDoesCharOccur.cpp
Normal file
48
CPP/miscelanious/howOftenDoesCharOccur.cpp
Normal 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;
|
||||||
|
}
|
||||||
177
CPP/miscelanious/markovChainGenerator/basic.cpp
Normal file
177
CPP/miscelanious/markovChainGenerator/basic.cpp
Normal 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
|
||||||
11
CPP/miscelanious/markovChainGenerator/loremIpsum.txt
Normal file
11
CPP/miscelanious/markovChainGenerator/loremIpsum.txt
Normal 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.
|
||||||
138
CPP/miscelanious/markovChainGenerator/main.cpp
Normal file
138
CPP/miscelanious/markovChainGenerator/main.cpp
Normal 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
|
||||||
27
CPP/miscelanious/mutiplicationWithoutStar/multiplication.cpp
Normal file
27
CPP/miscelanious/mutiplicationWithoutStar/multiplication.cpp
Normal 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;
|
||||||
|
}
|
||||||
96
CPP/miscelanious/quickchallenges.cpp
Normal file
96
CPP/miscelanious/quickchallenges.cpp
Normal 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];
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
10
CPP/miscelanious/randomDevice/main.cpp
Normal file
10
CPP/miscelanious/randomDevice/main.cpp
Normal 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";
|
||||||
|
}
|
||||||
22
CPP/miscelanious/reverseString.cpp
Normal file
22
CPP/miscelanious/reverseString.cpp
Normal 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;
|
||||||
|
}
|
||||||
48
CPP/miscelanious/solveQuadraticEquation.cpp
Normal file
48
CPP/miscelanious/solveQuadraticEquation.cpp
Normal 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;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
111
CPP/miscelanious/tictactoe/tictactoe.cpp
Normal file
111
CPP/miscelanious/tictactoe/tictactoe.cpp
Normal 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;
|
||||||
|
}
|
||||||
89
CPP/miscelanious/tierListConverter/tierListConverter.cpp
Normal file
89
CPP/miscelanious/tierListConverter/tierListConverter.cpp
Normal 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;
|
||||||
|
}
|
||||||
11
CPP/miscelanious/xGoesTo0/xgoes.cpp
Normal file
11
CPP/miscelanious/xGoesTo0/xgoes.cpp
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
int x = 10;
|
||||||
|
while (x-- > 0)
|
||||||
|
{
|
||||||
|
printf("%d;", x);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
22
CPP/miscelanious/yousuckatcards/Bernouli/bernouli.cpp
Normal file
22
CPP/miscelanious/yousuckatcards/Bernouli/bernouli.cpp
Normal 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;
|
||||||
|
}
|
||||||
13
CPP/miscelanious/yousuckatcards/Bernouli/test.cpp
Normal file
13
CPP/miscelanious/yousuckatcards/Bernouli/test.cpp
Normal 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;
|
||||||
|
}
|
||||||
2
CPP/miscelanious/yousuckatcards/makefile
Normal file
2
CPP/miscelanious/yousuckatcards/makefile
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
yousuckatcards:
|
||||||
|
g++ -Wall -Wextra -pedantic yousuckatcards.cpp
|
||||||
127
CPP/miscelanious/yousuckatcards/yousuckatcards.cpp
Normal file
127
CPP/miscelanious/yousuckatcards/yousuckatcards.cpp
Normal 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;
|
||||||
|
}
|
||||||
8
CPP/tests/howCppHandlesDivision.cpp
Normal file
8
CPP/tests/howCppHandlesDivision.cpp
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
float X = 1/2;
|
||||||
|
std::cout << X << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
34
PYTHON/downloadCats/generate_cats.py
Normal file
34
PYTHON/downloadCats/generate_cats.py
Normal 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}")
|
||||||
4
PYTHON/downloadCats/requirements.txt
Normal file
4
PYTHON/downloadCats/requirements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
json
|
||||||
|
os
|
||||||
|
pathlib
|
||||||
|
requests
|
||||||
23
PYTHON/extractLinks/main.py
Normal file → Executable file
23
PYTHON/extractLinks/main.py
Normal file → Executable 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,25 +11,24 @@ 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:
|
||||||
@ -41,8 +39,8 @@ def extract_hosts_from_html(html_text: str) -> List[str]:
|
|||||||
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:
|
||||||
@ -54,7 +52,9 @@ def extract_hosts_from_html(html_text: str) -> List[str]:
|
|||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
ap = argparse.ArgumentParser(description="Extract hosts from hrefs in an HTML file.")
|
ap = argparse.ArgumentParser(
|
||||||
|
description="Extract hosts from hrefs in an HTML file."
|
||||||
|
)
|
||||||
ap.add_argument("input_html", help="Path to input HTML file")
|
ap.add_argument("input_html", help="Path to input HTML file")
|
||||||
ap.add_argument(
|
ap.add_argument(
|
||||||
"output_txt",
|
"output_txt",
|
||||||
@ -72,7 +72,7 @@ def main() -> int:
|
|||||||
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)
|
||||||
@ -87,4 +87,3 @@ def main() -> int:
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
raise SystemExit(main())
|
raise SystemExit(main())
|
||||||
|
|
||||||
|
|||||||
@ -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]
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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))
|
||||||
@ -85,8 +87,10 @@ class KeyboardCoopGame:
|
|||||||
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,31 +98,103 @@ 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
|
||||||
@ -137,9 +213,14 @@ class KeyboardCoopGame:
|
|||||||
|
|
||||||
# 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:
|
||||||
@ -203,13 +284,19 @@ class KeyboardCoopGame:
|
|||||||
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:
|
||||||
@ -280,7 +367,9 @@ class KeyboardCoopGame:
|
|||||||
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
|
||||||
@ -289,7 +378,9 @@ class KeyboardCoopGame:
|
|||||||
|
|
||||||
# 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
|
||||||
@ -305,7 +396,7 @@ 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
|
||||||
@ -383,6 +474,7 @@ class KeyboardCoopGame:
|
|||||||
pygame.quit()
|
pygame.quit()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
game = KeyboardCoopGame()
|
game = KeyboardCoopGame()
|
||||||
game.run()
|
game.run()
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
"""Package marker for lichess_bot."""
|
"""Package marker for lichess_bot."""
|
||||||
|
|
||||||
__all__ = []
|
__all__ = []
|
||||||
|
|||||||
@ -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_index": data.get("chosen_index"),
|
||||||
"chosen_move": data.get("chosen_move"),
|
"chosen_move": data.get("chosen_move"),
|
||||||
}, ensure_ascii=False)
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
# Leave defaults with raw output text
|
# Leave defaults with raw output text
|
||||||
pass
|
pass
|
||||||
|
|||||||
@ -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}",
|
"Authorization": f"Bearer {self.token}",
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
"User-Agent": "minimal-lichess-bot/0.1 (+https://lichess.org)"
|
"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:
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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}",
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import builtins
|
|
||||||
|
|
||||||
from PYTHON.lichess_bot.utils import backoff_sleep
|
from PYTHON.lichess_bot.utils import backoff_sleep
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
80
PYTHON/lichess_bot/tools/generate_blunder_tests.py
Normal file → Executable file
80
PYTHON/lichess_bot/tools/generate_blunder_tests.py
Normal file → Executable 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
|
||||||
@ -52,7 +50,7 @@ class 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,7 +58,7 @@ 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():
|
||||||
@ -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,9 +367,8 @@ 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
|
||||||
else:
|
|
||||||
# Treat as game id, resolve within past_games
|
# Treat as game id, resolve within past_games
|
||||||
if re.fullmatch(r"[A-Za-z0-9]+", arg):
|
elif re.fullmatch(r"[A-Za-z0-9]+", arg):
|
||||||
candidate_path = os.path.join(past_dir, f"lichess_bot_game_{arg}.log")
|
candidate_path = os.path.join(past_dir, f"lichess_bot_game_{arg}.log")
|
||||||
else:
|
else:
|
||||||
# Fallback: if it's a bare filename, try inside past_games
|
# Fallback: if it's a bare filename, try inside past_games
|
||||||
|
|||||||
@ -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
PYTHON/mock_server/README.md
Normal file
54
PYTHON/mock_server/README.md
Normal 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
|
||||||
11
PYTHON/mock_server/mock_server.py
Normal file
11
PYTHON/mock_server/mock_server.py
Normal 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"},
|
||||||
|
)
|
||||||
1
PYTHON/randomJPG/Readme.md
Normal file
1
PYTHON/randomJPG/Readme.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
Did you ever need to generate random jpg images with huge file size? Now you can!
|
||||||
133
PYTHON/randomJPG/generateJpeg.py
Normal file
133
PYTHON/randomJPG/generateJpeg.py
Normal 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)}")
|
||||||
1
PYTHON/randomJPG/requirements.txt
Normal file
1
PYTHON/randomJPG/requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
pillow
|
||||||
@ -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
63
PYTHON/scapeWebsite/.gitignore
vendored
Normal 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
|
||||||
2
PYTHON/scapeWebsite/requirements.txt
Normal file
2
PYTHON/scapeWebsite/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
requests
|
||||||
|
selenium
|
||||||
75
PYTHON/scapeWebsite/scrape_comics.py
Normal file
75
PYTHON/scapeWebsite/scrape_comics.py
Normal 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()
|
||||||
@ -1,23 +1,20 @@
|
|||||||
#!/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():
|
||||||
@ -27,7 +24,9 @@ class ScreenLocker:
|
|||||||
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
|
||||||
@ -38,12 +37,12 @@ class ScreenLocker:
|
|||||||
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
|
||||||
@ -51,17 +50,17 @@ class ScreenLocker:
|
|||||||
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()
|
||||||
@ -81,38 +80,38 @@ class ScreenLocker:
|
|||||||
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()
|
||||||
@ -120,18 +119,18 @@ class ScreenLocker:
|
|||||||
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)
|
||||||
|
|
||||||
@ -152,92 +151,106 @@ class ScreenLocker:
|
|||||||
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)
|
||||||
|
|
||||||
@ -245,12 +258,12 @@ class ScreenLocker:
|
|||||||
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)
|
||||||
|
|
||||||
@ -285,7 +298,9 @@ class ScreenLocker:
|
|||||||
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
|
||||||
@ -296,71 +311,97 @@ class ScreenLocker:
|
|||||||
|
|
||||||
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)
|
||||||
|
|
||||||
@ -368,32 +409,40 @@ class ScreenLocker:
|
|||||||
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
|
||||||
@ -415,12 +464,16 @@ class ScreenLocker:
|
|||||||
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
|
||||||
@ -434,7 +487,9 @@ class ScreenLocker:
|
|||||||
# 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:
|
||||||
@ -445,9 +500,9 @@ class ScreenLocker:
|
|||||||
# 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:
|
||||||
@ -466,9 +521,9 @@ class ScreenLocker:
|
|||||||
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:
|
||||||
@ -484,31 +539,31 @@ class ScreenLocker:
|
|||||||
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)
|
||||||
|
|
||||||
@ -521,18 +576,18 @@ class ScreenLocker:
|
|||||||
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)
|
||||||
|
|
||||||
@ -544,12 +599,12 @@ class ScreenLocker:
|
|||||||
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):
|
||||||
@ -558,23 +613,23 @@ class ScreenLocker:
|
|||||||
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):
|
||||||
@ -585,11 +640,11 @@ class ScreenLocker:
|
|||||||
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)
|
||||||
|
|||||||
20
PYTHON/screen_locker/workout_log.json
Normal file
20
PYTHON/screen_locker/workout_log.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
PYTHON/split/split_x_into_n_symmetrically.py
Normal file
57
PYTHON/split/split_x_into_n_symmetrically.py
Normal 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)
|
||||||
@ -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.
|
||||||
|
|||||||
209
PYTHON/stockfish_analysis/analyze_chess_game.py
Normal file → Executable file
209
PYTHON/stockfish_analysis/analyze_chess_game.py
Normal file → Executable 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,8 +474,7 @@ 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)
|
||||||
|
|
||||||
@ -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,8 +584,7 @@ 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)
|
||||||
|
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
python-chess>=1.999
|
|
||||||
psutil>=5.9
|
psutil>=5.9
|
||||||
|
python-chess>=1.999
|
||||||
|
|||||||
15
PYTHON/tagDivider/README.md
Normal file
15
PYTHON/tagDivider/README.md
Normal 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
|
||||||
72
PYTHON/tagDivider/tagDivider.py
Normal file
72
PYTHON/tagDivider/tagDivider.py
Normal 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()
|
||||||
@ -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.
|
||||||
@ -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
|
||||||
92
TS/two-inputs/angular.json
Normal file
92
TS/two-inputs/angular.json
Normal 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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
TS/two-inputs/package.json
Normal file
40
TS/two-inputs/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
30
TS/two-inputs/src/app/app.component.html
Normal file
30
TS/two-inputs/src/app/app.component.html
Normal 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>
|
||||||
138
TS/two-inputs/src/app/app.component.ts
Normal file
138
TS/two-inputs/src/app/app.component.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import {MatButtonModule} from '@angular/material/button';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, RouterOutlet, MatInputModule, FormsModule, MatButtonModule],
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss']
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
inputOne: number | null = null; // Default initialization to 50
|
||||||
|
inputTwo: number | null = null; // Default initialization to 50
|
||||||
|
min: number | null = 100;
|
||||||
|
max: number | null = 250;
|
||||||
|
step: number | null = 25;
|
||||||
|
targetValue: number | null = 325;
|
||||||
|
indexOne: number = 0;
|
||||||
|
indexTwo: number = 0;
|
||||||
|
possibleValues: Array<[number, number]> | null = [];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.possibleValues = AppComponent.findValidPairs(this.step, this.min, this.max, this.targetValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.possibleValues = AppComponent.findValidPairs(this.step, this.min, this.max, this.targetValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateInput() {
|
||||||
|
this.possibleValues = AppComponent.findValidPairs(this.step, this.min, this.max, this.targetValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private changeIndex(currentValue: number, direction: boolean) {
|
||||||
|
this.possibleValues = AppComponent.findValidPairs(this.step, this.min, this.max, this.targetValue);
|
||||||
|
const length = this.possibleValues?.length;
|
||||||
|
if(typeof length !== "undefined") {
|
||||||
|
if(direction) {
|
||||||
|
if(currentValue + 1 > length - 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return currentValue + 1;
|
||||||
|
} else {
|
||||||
|
if(currentValue - 1 < 0) {
|
||||||
|
return length - 1;
|
||||||
|
}
|
||||||
|
return currentValue - 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(`appComponent, changeIndex, length is undefined!`, length);
|
||||||
|
}
|
||||||
|
return currentValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTwoValue() {
|
||||||
|
if(this.possibleValues !== null) {
|
||||||
|
this.inputOne = this.possibleValues[this.indexOne][0];
|
||||||
|
if(typeof this.inputOne !== "undefined" && this.inputOne !== null) {
|
||||||
|
const result = AppComponent.findCorrespondingValue(this.possibleValues, this.inputOne);
|
||||||
|
if(result !== null) {
|
||||||
|
[this.inputTwo, this.indexTwo] = result;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.error(`result is null!`);
|
||||||
|
}
|
||||||
|
console.error(`this.inputOne is null or undefined!: `, this.inputOne, this.possibleValues, this.indexOne);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
upOne() {
|
||||||
|
this.indexOne = this.changeIndex(this.indexOne, true);
|
||||||
|
this.updateTwoValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
downOne() {
|
||||||
|
this.indexOne = this.changeIndex(this.indexOne, false);
|
||||||
|
this.updateTwoValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
upTwo() {
|
||||||
|
this.indexTwo = this.changeIndex(this.indexTwo, true);
|
||||||
|
if(this.possibleValues !== null) {
|
||||||
|
this.inputTwo = this.possibleValues[this.indexTwo][1];
|
||||||
|
const result = AppComponent.findCorrespondingValue(this.possibleValues, this.inputTwo);
|
||||||
|
if(result !== null) {
|
||||||
|
[this.inputOne, this.indexOne] = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downTwo() {
|
||||||
|
this.indexTwo = this.changeIndex(this.indexTwo, false);
|
||||||
|
if(this.possibleValues !== null) {
|
||||||
|
this.inputTwo = this.possibleValues[this.indexTwo][1];
|
||||||
|
const result = AppComponent.findCorrespondingValue(this.possibleValues, this.inputTwo);
|
||||||
|
if(result !== null) {
|
||||||
|
[this.inputOne, this.indexOne] = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static findCorrespondingValue(pairs: Array<[number, number]>, number: number): [number, number] | null {
|
||||||
|
for (let index = 0; index < pairs.length; index += 1) {
|
||||||
|
if (pairs[index][0] === number) {
|
||||||
|
return [pairs[index][1], index]; // Return n2 if the given number matches n1
|
||||||
|
} else if (pairs[index][1] === number) {
|
||||||
|
return [pairs[index][0], index]; // Return n1 if the given number matches n2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.error("No corresponding value found for the provided number in the pairs.", pairs, number);
|
||||||
|
return null; // Return null if no matching number is found
|
||||||
|
}
|
||||||
|
|
||||||
|
private static findValidPairs(x: number | null, y: number | null, z: number | null, ml: number | null): Array<[number, number]> | null {
|
||||||
|
if (x === null || y === null || z === null || ml === null) {
|
||||||
|
console.error("findValidPairs, some value is null");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const results: Array<[number, number]> = [];
|
||||||
|
|
||||||
|
// Iterate through possible values of n1, which must be multiples of x, at least y, and not more than z
|
||||||
|
for (let n1 = y; n1 <= ml - y && n1 <= z; n1 += x) {
|
||||||
|
let n2 = ml - n1;
|
||||||
|
// Ensure n2 is also a multiple of x, n2 >= y, and n2 <= z
|
||||||
|
if (n2 % x === 0 && n2 >= y && n2 <= z) {
|
||||||
|
results.push([n1, n2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
10
TS/two-inputs/tsconfig.app.json
Normal file
10
TS/two-inputs/tsconfig.app.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/app",
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"files": ["src/main.ts"],
|
||||||
|
"include": ["src/**/*.d.ts"]
|
||||||
|
}
|
||||||
29
TS/two-inputs/tsconfig.json
Normal file
29
TS/two-inputs/tsconfig.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"compileOnSave": false,
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/out-tsc",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": false,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"importHelpers": true,
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ES2022",
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"lib": ["ES2022", "dom"]
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"strictTemplates": true
|
||||||
|
}
|
||||||
|
}
|
||||||
9
TS/two-inputs/tsconfig.spec.json
Normal file
9
TS/two-inputs/tsconfig.spec.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/spec",
|
||||||
|
"types": ["jasmine"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
|
||||||
|
}
|
||||||
@ -9,7 +9,9 @@ Mini Articles (<=14KB)
|
|||||||
- Local persistence via localStorage (no server required)
|
- Local persistence via localStorage (no server required)
|
||||||
|
|
||||||
How to open
|
How to open
|
||||||
|
|
||||||
- Open `site/index.html` in a browser.
|
- Open `site/index.html` in a browser.
|
||||||
|
|
||||||
Tests
|
Tests
|
||||||
|
|
||||||
- `pytest` includes a test to enforce the 14KB budget for `index.html`.
|
- `pytest` includes a test to enforce the 14KB budget for `index.html`.
|
||||||
|
|||||||
@ -476,4 +476,3 @@ server_c.c:44:12: style: The function 'append_file_line' is never used. [unusedF
|
|||||||
static int append_file_line(const char* path, const char* line){ FILE* f=fopen(path,"ab"); if(!f) return -1; size_t n=fwrite(line,1,strlen(line),f); n+=fwrite("\n",1,1,f); fclose(f); return (int)n>=0?0:-1; }
|
static int append_file_line(const char* path, const char* line){ FILE* f=fopen(path,"ab"); if(!f) return -1; size_t n=fwrite(line,1,strlen(line),f); n+=fwrite("\n",1,1,f); fclose(f); return (int)n>=0?0:-1; }
|
||||||
^
|
^
|
||||||
nofile:0:0: information: Active checkers: 117/966 (use --checkers-report=<filename> to see details) [checkersReport]
|
nofile:0:0: information: Active checkers: 117/966 (use --checkers-report=<filename> to see details) [checkersReport]
|
||||||
|
|
||||||
|
|||||||
@ -1 +1,24 @@
|
|||||||
[{"id":"29176c917f1a66c3","title":"full featuresd article","author":"author","body":"<img loading=\"lazy\" decoding=\"async\" src=\"/uploads/29176c9e5c008daf.jpg\" alt=\"image\"><div><br></div><div>This is an important image of course :)</div><div><br></div><div>and nother:<br><img loading=\"lazy\" decoding=\"async\" src=\"/uploads/29176c9f62367245.jpg\" alt=\"image\"><br></div>","thumb":"/uploads/29176c9e7818ee30.jpg","createdAt":1757331025041},{"id":"199259f41b8933b8","title":"Whats heveier","body":"A kilogrem of stel or kilogrem of fethers","thumb":"/uploads/27d6402e78a09d65.jpg","createdAt":1757272818104},{"id":"19925965dead21e7","title":"UwU its my first article","body":":)))<div><img src=\"/uploads/27d640335a16f85f.jpg\"><br></div>","thumb":"/uploads/27d640310e163446.jpg","createdAt":1757272235498}]
|
[
|
||||||
|
{
|
||||||
|
"id": "29176c917f1a66c3",
|
||||||
|
"title": "full featuresd article",
|
||||||
|
"author": "author",
|
||||||
|
"body": "<img loading=\"lazy\" decoding=\"async\" src=\"/uploads/29176c9e5c008daf.jpg\" alt=\"image\"><div><br></div><div>This is an important image of course :)</div><div><br></div><div>and nother:<br><img loading=\"lazy\" decoding=\"async\" src=\"/uploads/29176c9f62367245.jpg\" alt=\"image\"><br></div>",
|
||||||
|
"thumb": "/uploads/29176c9e7818ee30.jpg",
|
||||||
|
"createdAt": 1757331025041
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "199259f41b8933b8",
|
||||||
|
"title": "Whats heveier",
|
||||||
|
"body": "A kilogrem of stel or kilogrem of fethers",
|
||||||
|
"thumb": "/uploads/27d6402e78a09d65.jpg",
|
||||||
|
"createdAt": 1757272818104
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "19925965dead21e7",
|
||||||
|
"title": "UwU its my first article",
|
||||||
|
"body": ":)))<div><img src=\"/uploads/27d640335a16f85f.jpg\"><br></div>",
|
||||||
|
"thumb": "/uploads/27d640310e163446.jpg",
|
||||||
|
"createdAt": 1757272235498
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import urllib.request
|
|
||||||
import urllib.error
|
|
||||||
import subprocess
|
|
||||||
import socket
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
|
||||||
def _req(url, method="GET", data=None):
|
def _req(url, method="GET", data=None):
|
||||||
@ -40,18 +40,24 @@ def test_crud_roundtrip(tmp_path):
|
|||||||
# wait briefly for server to be ready
|
# wait briefly for server to be ready
|
||||||
for _ in range(30):
|
for _ in range(30):
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(base + "/api/articles", timeout=0.2) as resp:
|
with urllib.request.urlopen(
|
||||||
|
base + "/api/articles", timeout=0.2
|
||||||
|
) as resp:
|
||||||
resp.read()
|
resp.read()
|
||||||
break
|
break
|
||||||
except Exception:
|
except Exception:
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
|
|
||||||
# Create
|
# Create
|
||||||
code, body = _req(base+"/api/articles", method="POST", data={
|
code, body = _req(
|
||||||
|
base + "/api/articles",
|
||||||
|
method="POST",
|
||||||
|
data={
|
||||||
"title": "T1",
|
"title": "T1",
|
||||||
"body": "<p>Hello</p>",
|
"body": "<p>Hello</p>",
|
||||||
"thumb": "data:image/png;base64,xyz"
|
"thumb": "data:image/png;base64,xyz",
|
||||||
})
|
},
|
||||||
|
)
|
||||||
assert code == 201
|
assert code == 201
|
||||||
created = json.loads(body)
|
created = json.loads(body)
|
||||||
art_id = created["id"]
|
art_id = created["id"]
|
||||||
@ -69,7 +75,9 @@ def test_crud_roundtrip(tmp_path):
|
|||||||
assert got["title"] == "T1"
|
assert got["title"] == "T1"
|
||||||
|
|
||||||
# Update
|
# Update
|
||||||
code, body = _req(base+f"/api/articles/{art_id}", method="PUT", data={"title": "T2"})
|
code, body = _req(
|
||||||
|
base + f"/api/articles/{art_id}", method="PUT", data={"title": "T2"}
|
||||||
|
)
|
||||||
assert code == 200
|
assert code == 200
|
||||||
updated = json.loads(body)
|
updated = json.loads(body)
|
||||||
assert updated["title"] == "T2"
|
assert updated["title"] == "T2"
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import os
|
|||||||
BUDGET = 14 * 1024 # 14 KiB
|
BUDGET = 14 * 1024 # 14 KiB
|
||||||
|
|
||||||
HERE = os.path.dirname(__file__)
|
HERE = os.path.dirname(__file__)
|
||||||
SITE_FILE = os.path.join(HERE, 'index.html')
|
SITE_FILE = os.path.join(HERE, "index.html")
|
||||||
|
|
||||||
|
|
||||||
def test_site_file_exists():
|
def test_site_file_exists():
|
||||||
|
|||||||
352
lint_python.sh
Executable file
352
lint_python.sh
Executable file
@ -0,0 +1,352 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ==============================================================================
|
||||||
|
# Python Linting Script - Run ALL linters with aggressive settings
|
||||||
|
# ==============================================================================
|
||||||
|
# Usage:
|
||||||
|
# ./lint_python.sh # Lint all Python files
|
||||||
|
# ./lint_python.sh --fix # Lint and auto-fix where possible
|
||||||
|
# ./lint_python.sh <file.py> # Lint specific file
|
||||||
|
# ./lint_python.sh --quick # Quick lint (ruff + mypy only)
|
||||||
|
# ./lint_python.sh --report # Generate detailed reports
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
MAGENTA='\033[0;35m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
BOLD='\033[1m'
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="${SCRIPT_DIR}"
|
||||||
|
PYTHON_PATHS=(
|
||||||
|
"PYTHON"
|
||||||
|
"articles"
|
||||||
|
"poker-modifier-app"
|
||||||
|
"tests"
|
||||||
|
)
|
||||||
|
EXCLUDE_PATHS=(
|
||||||
|
".venv"
|
||||||
|
"__pycache__"
|
||||||
|
".git"
|
||||||
|
"Bash/ffmpeg-build"
|
||||||
|
".pytest_cache"
|
||||||
|
".ruff_cache"
|
||||||
|
".mypy_cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build exclude pattern for find
|
||||||
|
EXCLUDE_PATTERN=""
|
||||||
|
for path in "${EXCLUDE_PATHS[@]}"; do
|
||||||
|
EXCLUDE_PATTERN="${EXCLUDE_PATTERN} -path '*/${path}/*' -prune -o"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
FIX_MODE=false
|
||||||
|
QUICK_MODE=false
|
||||||
|
REPORT_MODE=false
|
||||||
|
TARGET_FILES=""
|
||||||
|
VERBOSE=false
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--fix|-f)
|
||||||
|
FIX_MODE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--quick|-q)
|
||||||
|
QUICK_MODE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--report|-r)
|
||||||
|
REPORT_MODE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--verbose|-v)
|
||||||
|
VERBOSE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
echo "Usage: $0 [OPTIONS] [FILES...]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " --fix, -f Auto-fix issues where possible"
|
||||||
|
echo " --quick, -q Quick mode (ruff + mypy only)"
|
||||||
|
echo " --report, -r Generate detailed reports to ./lint-reports/"
|
||||||
|
echo " --verbose, -v Show verbose output"
|
||||||
|
echo " --help, -h Show this help message"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 # Lint all Python files"
|
||||||
|
echo " $0 --fix # Lint and auto-fix"
|
||||||
|
echo " $0 PYTHON/ # Lint specific directory"
|
||||||
|
echo " $0 --quick --fix # Quick lint with auto-fix"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
TARGET_FILES="${TARGET_FILES} $1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# If no target specified, use default paths
|
||||||
|
if [[ -z "${TARGET_FILES}" ]]; then
|
||||||
|
TARGET_FILES="${PYTHON_PATHS[*]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create reports directory if needed
|
||||||
|
if [[ "${REPORT_MODE}" == true ]]; then
|
||||||
|
mkdir -p "${PROJECT_ROOT}/lint-reports"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Track overall status
|
||||||
|
OVERALL_STATUS=0
|
||||||
|
FAILED_TOOLS=()
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# Helper functions
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
print_header() {
|
||||||
|
echo ""
|
||||||
|
echo -e "${BOLD}${BLUE}══════════════════════════════════════════════════════════════${NC}"
|
||||||
|
echo -e "${BOLD}${BLUE} $1${NC}"
|
||||||
|
echo -e "${BOLD}${BLUE}══════════════════════════════════════════════════════════════${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_subheader() {
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}──────────────────────────────────────────────────────────────${NC}"
|
||||||
|
echo -e "${CYAN} $1${NC}"
|
||||||
|
echo -e "${CYAN}──────────────────────────────────────────────────────────────${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_success() {
|
||||||
|
echo -e "${GREEN}✓${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_warning() {
|
||||||
|
echo -e "${YELLOW}⚠${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_error() {
|
||||||
|
echo -e "${RED}✗${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_info() {
|
||||||
|
echo -e "${BLUE}ℹ${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_tool() {
|
||||||
|
local tool_name="$1"
|
||||||
|
local tool_cmd="$2"
|
||||||
|
local report_file="${PROJECT_ROOT}/lint-reports/${tool_name}.txt"
|
||||||
|
|
||||||
|
print_subheader "Running ${tool_name}..."
|
||||||
|
|
||||||
|
if [[ "${REPORT_MODE}" == true ]]; then
|
||||||
|
if eval "${tool_cmd}" 2>&1 | tee "${report_file}"; then
|
||||||
|
print_success "${tool_name} passed"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_error "${tool_name} found issues (see ${report_file})"
|
||||||
|
FAILED_TOOLS+=("${tool_name}")
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if eval "${tool_cmd}"; then
|
||||||
|
print_success "${tool_name} passed"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_error "${tool_name} found issues"
|
||||||
|
FAILED_TOOLS+=("${tool_name}")
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_tool() {
|
||||||
|
if command -v "$1" &> /dev/null; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_warning "$1 not found, skipping..."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# Main linting process
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
print_header "Python Linting Suite - Aggressive Mode"
|
||||||
|
echo ""
|
||||||
|
print_info "Target: ${TARGET_FILES}"
|
||||||
|
print_info "Fix mode: ${FIX_MODE}"
|
||||||
|
print_info "Quick mode: ${QUICK_MODE}"
|
||||||
|
print_info "Report mode: ${REPORT_MODE}"
|
||||||
|
|
||||||
|
cd "${PROJECT_ROOT}"
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# RUFF - Primary linter and formatter
|
||||||
|
# ==============================================================================
|
||||||
|
if check_tool ruff; then
|
||||||
|
if [[ "${FIX_MODE}" == true ]]; then
|
||||||
|
run_tool "ruff-lint" "ruff check --fix --show-fixes ${TARGET_FILES}" || OVERALL_STATUS=1
|
||||||
|
run_tool "ruff-format" "ruff format ${TARGET_FILES}" || OVERALL_STATUS=1
|
||||||
|
else
|
||||||
|
run_tool "ruff-lint" "ruff check ${TARGET_FILES}" || OVERALL_STATUS=1
|
||||||
|
run_tool "ruff-format-check" "ruff format --check ${TARGET_FILES}" || OVERALL_STATUS=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# MYPY - Static type checking
|
||||||
|
# ==============================================================================
|
||||||
|
if check_tool mypy; then
|
||||||
|
run_tool "mypy" "mypy --strict --ignore-missing-imports ${TARGET_FILES}" || OVERALL_STATUS=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Quick mode exits here
|
||||||
|
if [[ "${QUICK_MODE}" == true ]]; then
|
||||||
|
print_header "Quick Lint Complete"
|
||||||
|
if [[ ${#FAILED_TOOLS[@]} -gt 0 ]]; then
|
||||||
|
print_error "Failed tools: ${FAILED_TOOLS[*]}"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
print_success "All quick checks passed!"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# PYLINT - Comprehensive linting
|
||||||
|
# ==============================================================================
|
||||||
|
if check_tool pylint; then
|
||||||
|
run_tool "pylint" "pylint --rcfile=pyproject.toml --jobs=0 --fail-under=0 ${TARGET_FILES}" || OVERALL_STATUS=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# BANDIT - Security linting
|
||||||
|
# ==============================================================================
|
||||||
|
if check_tool bandit; then
|
||||||
|
run_tool "bandit" "bandit -c pyproject.toml -r ${TARGET_FILES} --severity-level low --confidence-level low" || OVERALL_STATUS=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# VULTURE - Dead code detection
|
||||||
|
# ==============================================================================
|
||||||
|
if check_tool vulture; then
|
||||||
|
run_tool "vulture" "vulture --min-confidence 80 ${TARGET_FILES}" || OVERALL_STATUS=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# FLAKE8 - Traditional linter
|
||||||
|
# ==============================================================================
|
||||||
|
if check_tool flake8; then
|
||||||
|
run_tool "flake8" "flake8 --max-line-length=88 --extend-ignore=E203,W503 --max-complexity=10 ${TARGET_FILES}" || OVERALL_STATUS=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# PYCODESTYLE - PEP 8 style checker
|
||||||
|
# ==============================================================================
|
||||||
|
if check_tool pycodestyle; then
|
||||||
|
run_tool "pycodestyle" "pycodestyle --max-line-length=88 --ignore=E203,W503 ${TARGET_FILES}" || OVERALL_STATUS=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# PYDOCSTYLE - Docstring style checker
|
||||||
|
# ==============================================================================
|
||||||
|
if check_tool pydocstyle; then
|
||||||
|
run_tool "pydocstyle" "pydocstyle --convention=google ${TARGET_FILES}" || OVERALL_STATUS=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# RADON - Complexity metrics
|
||||||
|
# ==============================================================================
|
||||||
|
if check_tool radon; then
|
||||||
|
print_subheader "Running radon (complexity analysis)..."
|
||||||
|
echo ""
|
||||||
|
echo -e "${MAGENTA}Cyclomatic Complexity:${NC}"
|
||||||
|
radon cc -a -s ${TARGET_FILES} || true
|
||||||
|
echo ""
|
||||||
|
echo -e "${MAGENTA}Maintainability Index:${NC}"
|
||||||
|
radon mi -s ${TARGET_FILES} || true
|
||||||
|
|
||||||
|
if [[ "${REPORT_MODE}" == true ]]; then
|
||||||
|
radon cc -a -s ${TARGET_FILES} > "${PROJECT_ROOT}/lint-reports/radon-cc.txt" 2>&1 || true
|
||||||
|
radon mi -s ${TARGET_FILES} > "${PROJECT_ROOT}/lint-reports/radon-mi.txt" 2>&1 || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# INTERROGATE - Docstring coverage
|
||||||
|
# ==============================================================================
|
||||||
|
if check_tool interrogate; then
|
||||||
|
run_tool "interrogate" "interrogate -v --fail-under=0 ${TARGET_FILES}" || OVERALL_STATUS=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# PYRIGHT - Microsoft's type checker (optional, very strict)
|
||||||
|
# ==============================================================================
|
||||||
|
if check_tool pyright; then
|
||||||
|
run_tool "pyright" "pyright ${TARGET_FILES}" || OVERALL_STATUS=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# AUTOFLAKE - Unused imports/variables (fix mode only)
|
||||||
|
# ==============================================================================
|
||||||
|
if [[ "${FIX_MODE}" == true ]] && check_tool autoflake; then
|
||||||
|
print_subheader "Running autoflake (removing unused imports)..."
|
||||||
|
find ${TARGET_FILES} -name "*.py" -type f -exec autoflake --in-place --remove-all-unused-imports --remove-unused-variables {} \;
|
||||||
|
print_success "autoflake completed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# PYUPGRADE - Upgrade Python syntax (fix mode only)
|
||||||
|
# ==============================================================================
|
||||||
|
if [[ "${FIX_MODE}" == true ]] && check_tool pyupgrade; then
|
||||||
|
print_subheader "Running pyupgrade (upgrading syntax to Python 3.10+)..."
|
||||||
|
find ${TARGET_FILES} -name "*.py" -type f -exec pyupgrade --py310-plus {} \;
|
||||||
|
print_success "pyupgrade completed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# CODESPELL - Spell checking
|
||||||
|
# ==============================================================================
|
||||||
|
if check_tool codespell; then
|
||||||
|
if [[ "${FIX_MODE}" == true ]]; then
|
||||||
|
run_tool "codespell" "codespell -w --skip='*.json,*.lock,.git,__pycache__,.venv' ${TARGET_FILES}" || OVERALL_STATUS=1
|
||||||
|
else
|
||||||
|
run_tool "codespell" "codespell --skip='*.json,*.lock,.git,__pycache__,.venv' ${TARGET_FILES}" || OVERALL_STATUS=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# Summary
|
||||||
|
# ==============================================================================
|
||||||
|
print_header "Linting Summary"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ ${#FAILED_TOOLS[@]} -gt 0 ]]; then
|
||||||
|
print_error "The following tools reported issues:"
|
||||||
|
for tool in "${FAILED_TOOLS[@]}"; do
|
||||||
|
echo " - ${tool}"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
if [[ "${REPORT_MODE}" == true ]]; then
|
||||||
|
print_info "Detailed reports saved to: ${PROJECT_ROOT}/lint-reports/"
|
||||||
|
fi
|
||||||
|
print_info "Run with --fix to auto-fix issues where possible"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
print_success "All linting checks passed!"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
@ -47,7 +47,10 @@ A fun web application that randomly applies modifiers to Texas Hold'em poker gam
|
|||||||
You can easily add new modifiers by using the `addModifier()` method:
|
You can easily add new modifiers by using the `addModifier()` method:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
window.pokerApp.addModifier("Your Modifier Name", "Description of what it does");
|
window.pokerApp.addModifier(
|
||||||
|
"Your Modifier Name",
|
||||||
|
"Description of what it does",
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Browser Compatibility
|
## Browser Compatibility
|
||||||
|
|||||||
@ -32,6 +32,7 @@ python poker_modifier_app.py
|
|||||||
## Modifiers Included
|
## Modifiers Included
|
||||||
|
|
||||||
### Classic Poker Modifiers
|
### Classic Poker Modifiers
|
||||||
|
|
||||||
- **High Stakes**: All bets are doubled
|
- **High Stakes**: All bets are doubled
|
||||||
- **Wild Card**: Next card can be used as any card
|
- **Wild Card**: Next card can be used as any card
|
||||||
- **Bluff Master**: See one opponent's card before betting
|
- **Bluff Master**: See one opponent's card before betting
|
||||||
@ -51,6 +52,7 @@ python poker_modifier_app.py
|
|||||||
## Modifiers Included
|
## Modifiers Included
|
||||||
|
|
||||||
### Classic Poker Modifiers
|
### Classic Poker Modifiers
|
||||||
|
|
||||||
- **High Stakes**: All bets are doubled
|
- **High Stakes**: All bets are doubled
|
||||||
- **Wild Card**: Next card can be used as any card
|
- **Wild Card**: Next card can be used as any card
|
||||||
- **Bluff Master**: See one opponent's card before betting
|
- **Bluff Master**: See one opponent's card before betting
|
||||||
@ -68,6 +70,7 @@ python poker_modifier_app.py
|
|||||||
- **Chip Challenge**: Winner gets extra house chips
|
- **Chip Challenge**: Winner gets extra house chips
|
||||||
|
|
||||||
### Drinking Game Modifiers
|
### Drinking Game Modifiers
|
||||||
|
|
||||||
- **Red or Black**: Guess community card colors for double winnings
|
- **Red or Black**: Guess community card colors for double winnings
|
||||||
- **Pocket Rockets**: Pocket Aces trigger drinks for everyone else
|
- **Pocket Rockets**: Pocket Aces trigger drinks for everyone else
|
||||||
- **Rainbow Flop**: 3-suit flop boosts flush draws
|
- **Rainbow Flop**: 3-suit flop boosts flush draws
|
||||||
|
|||||||
299
pyproject.toml
Normal file
299
pyproject.toml
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
[project]
|
||||||
|
name = "testsandmisc"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Collection of miscellaneous tests and scripts"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# RUFF - Extremely fast Python linter and formatter (written in Rust)
|
||||||
|
# ============================================================================
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 88 # Black-compatible line length (stricter)
|
||||||
|
target-version = "py310"
|
||||||
|
# Include all Python files
|
||||||
|
include = ["*.py", "**/*.py"]
|
||||||
|
# Exclude vendored/build directories
|
||||||
|
exclude = [
|
||||||
|
".git",
|
||||||
|
".venv",
|
||||||
|
"__pycache__",
|
||||||
|
"build",
|
||||||
|
"dist",
|
||||||
|
".eggs",
|
||||||
|
"Bash/ffmpeg-build", # Vendored FFmpeg tools
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
# AGGRESSIVE: Select ALL rules from all categories
|
||||||
|
select = ["ALL"]
|
||||||
|
# Minimal ignores - only conflicting rules
|
||||||
|
ignore = [
|
||||||
|
"D203", # 1 blank line required before class docstring (conflicts with D211)
|
||||||
|
"D213", # Multi-line docstring summary should start at second line (conflicts with D212)
|
||||||
|
"COM812", # Trailing comma missing (conflicts with formatter)
|
||||||
|
"ISC001", # Implicit string concatenation (conflicts with formatter)
|
||||||
|
"ANN101", # Missing type annotation for self (deprecated)
|
||||||
|
"ANN102", # Missing type annotation for cls (deprecated)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Allow ALL rules to be auto-fixed
|
||||||
|
fixable = ["ALL"]
|
||||||
|
unfixable = []
|
||||||
|
|
||||||
|
# Per-file ignores for test files
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"**/tests/**/*.py" = [
|
||||||
|
"S101", # Allow assert in tests
|
||||||
|
"PLR2004", # Allow magic values in tests
|
||||||
|
"D100", # Allow missing module docstring in tests
|
||||||
|
"D103", # Allow missing function docstring in tests
|
||||||
|
]
|
||||||
|
"**/conftest.py" = [
|
||||||
|
"D100", # Allow missing module docstring
|
||||||
|
"D103", # Allow missing function docstring
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.lint.pydocstyle]
|
||||||
|
convention = "google" # Use Google docstring convention
|
||||||
|
|
||||||
|
[tool.ruff.lint.isort]
|
||||||
|
force-single-line = false
|
||||||
|
force-sort-within-sections = true
|
||||||
|
known-first-party = ["PYTHON"]
|
||||||
|
|
||||||
|
[tool.ruff.lint.flake8-quotes]
|
||||||
|
docstring-quotes = "double"
|
||||||
|
inline-quotes = "double"
|
||||||
|
|
||||||
|
[tool.ruff.lint.flake8-tidy-imports]
|
||||||
|
ban-relative-imports = "all"
|
||||||
|
|
||||||
|
[tool.ruff.lint.pylint]
|
||||||
|
max-args = 5
|
||||||
|
max-branches = 12
|
||||||
|
max-returns = 6
|
||||||
|
max-statements = 50
|
||||||
|
|
||||||
|
[tool.ruff.lint.mccabe]
|
||||||
|
max-complexity = 10
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
quote-style = "double"
|
||||||
|
indent-style = "space"
|
||||||
|
skip-magic-trailing-comma = false
|
||||||
|
line-ending = "auto"
|
||||||
|
docstring-code-format = true
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# MYPY - Static type checker (most aggressive settings)
|
||||||
|
# ============================================================================
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.10"
|
||||||
|
# Strict mode enables most checks
|
||||||
|
strict = true
|
||||||
|
# Additional aggressive settings
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_untyped_decorators = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
warn_redundant_casts = true
|
||||||
|
warn_unused_ignores = true
|
||||||
|
warn_no_return = true
|
||||||
|
warn_unreachable = true
|
||||||
|
# Extra strict settings
|
||||||
|
disallow_any_unimported = true
|
||||||
|
disallow_any_explicit = false # Too aggressive for practical use
|
||||||
|
disallow_any_generics = true
|
||||||
|
disallow_subclassing_any = true
|
||||||
|
strict_equality = true
|
||||||
|
extra_checks = true
|
||||||
|
# Allow missing imports for third-party packages
|
||||||
|
ignore_missing_imports = true
|
||||||
|
# Show error codes
|
||||||
|
show_error_codes = true
|
||||||
|
# Enable colored output
|
||||||
|
color_output = true
|
||||||
|
# Exclude vendored directories
|
||||||
|
exclude = [
|
||||||
|
"Bash/ffmpeg-build/",
|
||||||
|
".venv/",
|
||||||
|
]
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# PYLINT - Comprehensive Python linter
|
||||||
|
# ============================================================================
|
||||||
|
[tool.pylint.main]
|
||||||
|
# Analyse import fallback blocks
|
||||||
|
analyse-fallback-blocks = true
|
||||||
|
# Pickle collected data for later comparisons
|
||||||
|
persistent = true
|
||||||
|
# Jobs to use for parallel execution (0 = auto)
|
||||||
|
jobs = 0
|
||||||
|
# Minimum Python version
|
||||||
|
py-version = "3.10"
|
||||||
|
# Ignore vendored directories
|
||||||
|
ignore = ["Bash", ".venv", "__pycache__"]
|
||||||
|
# Ignore patterns
|
||||||
|
ignore-patterns = [".*\\.pyi$"]
|
||||||
|
|
||||||
|
[tool.pylint.messages_control]
|
||||||
|
# Enable all checks by disabling disable
|
||||||
|
enable = "all"
|
||||||
|
# Minimal disabled checks - only truly problematic ones
|
||||||
|
disable = [
|
||||||
|
"raw-checker-failed",
|
||||||
|
"bad-inline-option",
|
||||||
|
"locally-disabled",
|
||||||
|
"file-ignored",
|
||||||
|
"suppressed-message",
|
||||||
|
"useless-suppression",
|
||||||
|
"deprecated-pragma",
|
||||||
|
"use-symbolic-message-instead",
|
||||||
|
"too-few-public-methods", # Often false positive for data classes
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.pylint.format]
|
||||||
|
# Maximum line length (same as ruff/black)
|
||||||
|
max-line-length = 88
|
||||||
|
# Maximum module lines
|
||||||
|
max-module-lines = 1000
|
||||||
|
|
||||||
|
[tool.pylint.design]
|
||||||
|
# Maximum arguments for function/method
|
||||||
|
max-args = 5
|
||||||
|
# Maximum local variables
|
||||||
|
max-locals = 15
|
||||||
|
# Maximum return statements
|
||||||
|
max-returns = 6
|
||||||
|
# Maximum branches in function body
|
||||||
|
max-branches = 12
|
||||||
|
# Maximum statements in function body
|
||||||
|
max-statements = 50
|
||||||
|
# Maximum parents for a class
|
||||||
|
max-parents = 7
|
||||||
|
# Maximum attributes for a class
|
||||||
|
max-attributes = 10
|
||||||
|
# Maximum public methods for a class
|
||||||
|
max-public-methods = 20
|
||||||
|
# Minimum public methods for a class
|
||||||
|
min-public-methods = 1
|
||||||
|
|
||||||
|
[tool.pylint.similarities]
|
||||||
|
# Minimum lines for similarity check
|
||||||
|
min-similarity-lines = 4
|
||||||
|
# Ignore comments when computing similarities
|
||||||
|
ignore-comments = true
|
||||||
|
# Ignore docstrings when computing similarities
|
||||||
|
ignore-docstrings = true
|
||||||
|
# Ignore imports when computing similarities
|
||||||
|
ignore-imports = true
|
||||||
|
|
||||||
|
[tool.pylint.spelling]
|
||||||
|
# No spelling dictionary to avoid false positives
|
||||||
|
spelling-dict = ""
|
||||||
|
|
||||||
|
[tool.pylint.typecheck]
|
||||||
|
# Ignore missing members for dynamic modules
|
||||||
|
ignored-modules = ["chess", "pygame", "cv2", "PIL"]
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# BANDIT - Security linter
|
||||||
|
# ============================================================================
|
||||||
|
[tool.bandit]
|
||||||
|
# Exclude test directories and vendored code
|
||||||
|
exclude_dirs = ["tests", ".venv", "Bash/ffmpeg-build"]
|
||||||
|
# Use all tests
|
||||||
|
tests = []
|
||||||
|
# No skipped tests (most aggressive)
|
||||||
|
skips = []
|
||||||
|
# Severity level: all (1=low, 2=medium, 3=high)
|
||||||
|
severity = 1
|
||||||
|
# Confidence level: all
|
||||||
|
confidence = 1
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# BLACK - Code formatter (for comparison/fallback)
|
||||||
|
# ============================================================================
|
||||||
|
[tool.black]
|
||||||
|
line-length = 88
|
||||||
|
target-version = ["py310"]
|
||||||
|
include = '\.pyi?$'
|
||||||
|
extend-exclude = '''
|
||||||
|
/(
|
||||||
|
\.git
|
||||||
|
| \.venv
|
||||||
|
| __pycache__
|
||||||
|
| Bash/ffmpeg-build
|
||||||
|
)/
|
||||||
|
'''
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# ISORT - Import sorting (for comparison/fallback, ruff handles this)
|
||||||
|
# ============================================================================
|
||||||
|
[tool.isort]
|
||||||
|
profile = "black"
|
||||||
|
line_length = 88
|
||||||
|
force_single_line = false
|
||||||
|
force_sort_within_sections = true
|
||||||
|
known_first_party = ["PYTHON"]
|
||||||
|
skip = [".venv", "__pycache__", "Bash/ffmpeg-build"]
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# PYTEST - Testing framework configuration
|
||||||
|
# ============================================================================
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests", "PYTHON", "articles"]
|
||||||
|
python_files = ["test_*.py", "*_test.py"]
|
||||||
|
python_classes = ["Test*"]
|
||||||
|
python_functions = ["test_*"]
|
||||||
|
addopts = [
|
||||||
|
"-v",
|
||||||
|
"--strict-markers",
|
||||||
|
"--strict-config",
|
||||||
|
"-ra",
|
||||||
|
]
|
||||||
|
filterwarnings = [
|
||||||
|
"error",
|
||||||
|
"ignore::DeprecationWarning",
|
||||||
|
]
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# COVERAGE - Code coverage configuration
|
||||||
|
# ============================================================================
|
||||||
|
[tool.coverage.run]
|
||||||
|
source = ["PYTHON", "articles", "poker-modifier-app"]
|
||||||
|
branch = true
|
||||||
|
omit = [
|
||||||
|
"*/__pycache__/*",
|
||||||
|
"*/tests/*",
|
||||||
|
"*/.venv/*",
|
||||||
|
"Bash/*",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
# Fail under this percentage
|
||||||
|
fail_under = 0
|
||||||
|
show_missing = true
|
||||||
|
skip_covered = false
|
||||||
|
exclude_lines = [
|
||||||
|
"pragma: no cover",
|
||||||
|
"def __repr__",
|
||||||
|
"raise AssertionError",
|
||||||
|
"raise NotImplementedError",
|
||||||
|
"if __name__ == .__main__.:",
|
||||||
|
"if TYPE_CHECKING:",
|
||||||
|
]
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# VULTURE - Dead code detection
|
||||||
|
# ============================================================================
|
||||||
|
# Note: Vulture uses command-line args, but we can document settings here
|
||||||
|
# vulture --min-confidence 80 --exclude ".venv,Bash" .
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# PYDOCSTYLE - Docstring style checker (ruff handles this, but for standalone)
|
||||||
|
# ============================================================================
|
||||||
|
# Configured in ruff.lint.pydocstyle above
|
||||||
191
requirements-dev.txt
Normal file
191
requirements-dev.txt
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
# ==============================================================================
|
||||||
|
# Python Development Dependencies - Linting, Formatting, and Testing
|
||||||
|
# ==============================================================================
|
||||||
|
# Install with: pip install -r requirements-dev.txt
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# Include base requirements
|
||||||
|
-r requirements.txt
|
||||||
|
|
||||||
|
# add-trailing-comma - Add trailing commas
|
||||||
|
add-trailing-comma>=3.1.0
|
||||||
|
|
||||||
|
# autoflake - Remove unused imports and variables
|
||||||
|
autoflake>=2.2.0
|
||||||
|
|
||||||
|
# autopep8 - PEP 8 formatting (alternative formatter)
|
||||||
|
autopep8>=2.0.0
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SECURITY LINTERS
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# Bandit - Security linter
|
||||||
|
bandit>=1.7.0
|
||||||
|
|
||||||
|
# Black - The uncompromising code formatter (fallback/comparison)
|
||||||
|
black>=24.0.0
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SPELL CHECKING
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# codespell - Fix common misspellings
|
||||||
|
codespell>=2.2.0
|
||||||
|
|
||||||
|
# Coverage.py - Code coverage measurement
|
||||||
|
coverage>=7.4.0
|
||||||
|
|
||||||
|
# darglint - Check docstrings match function signatures
|
||||||
|
darglint>=1.8.0
|
||||||
|
|
||||||
|
# dead - Find dead code
|
||||||
|
dead>=1.5.0
|
||||||
|
|
||||||
|
# docformatter - Formats docstrings
|
||||||
|
docformatter>=1.7.0
|
||||||
|
|
||||||
|
# fixit - Auto-fix linting errors
|
||||||
|
fixit>=2.1.0
|
||||||
|
|
||||||
|
# Flake8 - Linting tool (wraps pyflakes, pycodestyle, mccabe)
|
||||||
|
flake8>=7.0.0
|
||||||
|
flake8-annotations>=3.0.0 # Type annotation checks
|
||||||
|
flake8-bandit>=4.1.0 # Security checks via bandit
|
||||||
|
|
||||||
|
# Flake8 plugins for maximum coverage
|
||||||
|
flake8-bugbear>=24.0.0 # Additional bug detection
|
||||||
|
flake8-comprehensions>=3.14.0 # Better list/dict/set comprehensions
|
||||||
|
flake8-docstrings>=1.7.0 # Docstring checks
|
||||||
|
flake8-eradicate>=1.5.0 # Dead code detection
|
||||||
|
flake8-pie>=0.16.0 # Miscellaneous lints
|
||||||
|
flake8-print>=5.0.0 # Detect print statements
|
||||||
|
flake8-pyi>=24.0.0 # Type stub file checks
|
||||||
|
flake8-pytest-style>=2.0.0 # Pytest style checks
|
||||||
|
flake8-return>=1.2.0 # Better return statement checks
|
||||||
|
flake8-simplify>=0.21.0 # Simplification suggestions
|
||||||
|
|
||||||
|
# Hypothesis - Property-based testing
|
||||||
|
hypothesis>=6.98.0
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# IMPORT CHECKING
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# importlib-metadata for import analysis
|
||||||
|
importlib-metadata>=7.0.0
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# DOCUMENTATION
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# pep257 - PEP 257 docstring checker (legacy, use pydocstyle)
|
||||||
|
# interrogate - Check docstring coverage
|
||||||
|
interrogate>=1.5.0
|
||||||
|
|
||||||
|
# isort - Import sorting (ruff handles this, but useful standalone)
|
||||||
|
isort>=5.13.0
|
||||||
|
|
||||||
|
# mccabe - McCabe complexity checker
|
||||||
|
mccabe>=0.7.0
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# TYPE CHECKING
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# MyPy - Static type checker
|
||||||
|
mypy>=1.8.0
|
||||||
|
|
||||||
|
# pip-audit - Audit Python packages for known vulnerabilities
|
||||||
|
pip-audit>=2.6.0
|
||||||
|
|
||||||
|
# pipdeptree - Show dependency tree
|
||||||
|
pipdeptree>=2.14.0
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# PRE-COMMIT
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# pre-commit - Git hook management
|
||||||
|
pre-commit>=3.6.0
|
||||||
|
|
||||||
|
# prospector - Python static analysis tool
|
||||||
|
prospector>=1.10.0
|
||||||
|
|
||||||
|
# pycodestyle - Python style guide checker (PEP 8)
|
||||||
|
pycodestyle>=2.11.0
|
||||||
|
|
||||||
|
# pydocstyle - Docstring style checker (PEP 257)
|
||||||
|
pydocstyle>=6.3.0
|
||||||
|
|
||||||
|
# pyflakes - Passive checker of Python programs
|
||||||
|
pyflakes>=3.2.0
|
||||||
|
|
||||||
|
# pylama - Code audit tool (wraps multiple linters)
|
||||||
|
pylama>=8.4.0
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# LINTERS
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# Pylint - Comprehensive Python linter
|
||||||
|
pylint>=3.0.0
|
||||||
|
|
||||||
|
# Pyright - Microsoft's type checker (very strict)
|
||||||
|
pyright>=1.1.350
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# TESTING
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# pytest - Testing framework
|
||||||
|
pytest>=8.0.0
|
||||||
|
|
||||||
|
# pytest plugins
|
||||||
|
pytest-cov>=4.1.0 # Coverage plugin
|
||||||
|
pytest-randomly>=3.15.0 # Randomize test order
|
||||||
|
pytest-sugar>=1.0.0 # Better test output
|
||||||
|
pytest-timeout>=2.2.0 # Test timeouts
|
||||||
|
pytest-xdist>=3.5.0 # Parallel test execution
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# ADDITIONAL TOOLS
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# pyupgrade - Upgrade Python syntax
|
||||||
|
pyupgrade>=3.15.0
|
||||||
|
|
||||||
|
# Radon - Code metrics (complexity, maintainability)
|
||||||
|
radon>=6.0.0
|
||||||
|
|
||||||
|
# reorder-python-imports - Reorder imports
|
||||||
|
reorder-python-imports>=3.12.0
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# CODE FORMATTERS
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# Ruff - Extremely fast Python linter and formatter (replaces many tools)
|
||||||
|
ruff>=0.8.0
|
||||||
|
|
||||||
|
# Safety - Check dependencies for known security vulnerabilities
|
||||||
|
safety>=2.3.0
|
||||||
|
types-python-dateutil>=2.8.0
|
||||||
|
types-PyYAML>=6.0.0
|
||||||
|
|
||||||
|
# Type stubs for common packages
|
||||||
|
types-requests>=2.31.0
|
||||||
|
types-setuptools>=69.0.0
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# CODE QUALITY & DEAD CODE DETECTION
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# Vulture - Find dead code
|
||||||
|
vulture>=2.10
|
||||||
|
|
||||||
|
# xenon - Monitor code complexity
|
||||||
|
xenon>=0.9.0
|
||||||
|
|
||||||
|
# yapf - Yet Another Python Formatter (Google's formatter)
|
||||||
|
yapf>=0.40.0
|
||||||
@ -1,2 +1,2 @@
|
|||||||
python-chess>=1.999
|
|
||||||
pytest>=7.0
|
pytest>=7.0
|
||||||
|
python-chess>=1.999
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user