mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 13:23:15 +02:00
chore: archive 41 unmaintained directories to testsAndMisc-archive
Full per-directory history preserved at https://github.com/kuhyx/testsAndMisc-archive
This commit is contained in:
parent
84632cef34
commit
84e5d39137
@ -1,15 +1,3 @@
|
|||||||
# Binary files allowed in the repository.
|
# Binary files allowed in the repository.
|
||||||
# One glob pattern per line. These are essential for builds and cannot be external.
|
# One glob pattern per line. These are essential for builds and cannot be external.
|
||||||
# Lines starting with # are comments.
|
# Lines starting with # are comments.
|
||||||
|
|
||||||
# Flutter app launcher icons (required for Android builds)
|
|
||||||
pomodoro_app/android/app/src/main/res/mipmap-*/ic_launcher.png
|
|
||||||
|
|
||||||
# Pomodoro app packaging icon
|
|
||||||
pomodoro_app/packaging/arch/*.svg
|
|
||||||
|
|
||||||
# Battery status favicon
|
|
||||||
TS/battery-status/public/favicon.svg
|
|
||||||
|
|
||||||
# Horatio demo recordings (symlinks to ../testsAndMisc_binaries/)
|
|
||||||
horatio/horatio_app/assets/demo_recordings/*.wav
|
|
||||||
|
|||||||
@ -86,10 +86,6 @@ __pycache__/
|
|||||||
**/__pycache__/
|
**/__pycache__/
|
||||||
|
|
||||||
# Build outputs (symlinked to ../testsAndMisc_builds/)
|
# Build outputs (symlinked to ../testsAndMisc_builds/)
|
||||||
pomodoro_app/build/
|
|
||||||
horatio/horatio_app/build/
|
|
||||||
sonic_pi/build/
|
|
||||||
CPP/mini_browser/build/
|
|
||||||
**/build/
|
**/build/
|
||||||
|
|
||||||
# Dart / Flutter caches (symlinked to ../testsAndMisc_builds/)
|
# Dart / Flutter caches (symlinked to ../testsAndMisc_builds/)
|
||||||
@ -104,26 +100,12 @@ CPP/mini_browser/build/
|
|||||||
**/generated_images_*/
|
**/generated_images_*/
|
||||||
**/preview_images/
|
**/preview_images/
|
||||||
|
|
||||||
# Anki decks
|
|
||||||
python_pkg/anki_decks/
|
|
||||||
|
|
||||||
# Image-heavy directories (moved to ../testsAndMisc_binaries/)
|
|
||||||
python_pkg/praca_magisterska_video/images/
|
|
||||||
python_pkg/praca_magisterska_video/
|
|
||||||
python_pkg/articles/uploads/
|
|
||||||
python_pkg/download_cats/http_cat_cache/
|
|
||||||
C/misc/randomJPG/
|
|
||||||
|
|
||||||
# Files with many embedded image references
|
# Files with many embedded image references
|
||||||
python_pkg/cinema_planner/pasted_content.txt
|
python_pkg/cinema_planner/pasted_content.txt
|
||||||
|
|
||||||
# Large data files (exceed indexing limits)
|
# Large data files (exceed indexing limits)
|
||||||
python_pkg/keyboard_coop/words_dictionary.json
|
|
||||||
linux_configuration/scripts/digital_wellbeing/pacman/words.txt
|
linux_configuration/scripts/digital_wellbeing/pacman/words.txt
|
||||||
python_pkg/word_frequency/test_texts/
|
|
||||||
|
|
||||||
# C build artifacts
|
# C build artifacts
|
||||||
**/*.out
|
**/*.out
|
||||||
**/random_engine
|
|
||||||
**/fps_demo
|
**/fps_demo
|
||||||
**/1dvelocitysimulator
|
|
||||||
|
|||||||
81
.gitignore
vendored
81
.gitignore
vendored
@ -110,19 +110,6 @@ Thumbs.db
|
|||||||
*.dylib
|
*.dylib
|
||||||
|
|
||||||
# Explicitly allowed binary files (override above with !)
|
# Explicitly allowed binary files (override above with !)
|
||||||
!pomodoro_app/android/app/src/main/res/mipmap-*/ic_launcher.png
|
|
||||||
!pomodoro_app/packaging/arch/pomodoro-app.svg
|
|
||||||
!TS/battery-status/public/favicon.svg
|
|
||||||
|
|
||||||
# Binary asset symlinks (point to ../testsAndMisc_binaries/)
|
|
||||||
pomodoro_app/assets/sounds/long_break_done.wav
|
|
||||||
pomodoro_app/assets/sounds/long_break_start.wav
|
|
||||||
pomodoro_app/assets/sounds/short_break_done.wav
|
|
||||||
pomodoro_app/assets/sounds/work_done.wav
|
|
||||||
horatio/horatio_app/assets/demo_recordings/hamlet_line0_take1.wav
|
|
||||||
horatio/horatio_app/assets/demo_recordings/hamlet_line0_take2.wav
|
|
||||||
horatio/horatio_app/assets/demo_recordings/hamlet_line0_take3.wav
|
|
||||||
horatio/horatio_app/assets/demo_recordings/hamlet_line1_take1.wav
|
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
|
|
||||||
@ -337,48 +324,9 @@ Bash/ffmpeg-build/FFmpeg
|
|||||||
*.gcda
|
*.gcda
|
||||||
*.gcno
|
*.gcno
|
||||||
**/coverage.info
|
**/coverage.info
|
||||||
C/1dvelocitysimulator/test_physics
|
|
||||||
C/lichess_random_engine/test_movegen
|
|
||||||
C/lichess_random_engine/test_search
|
|
||||||
C/vocabulary_curve/test_vocabulary
|
|
||||||
CPP/miscelanious/test_challenges
|
|
||||||
|
|
||||||
# C/C++ compiled binaries
|
|
||||||
C/1dvelocitysimulator/1dvelocitysimulator
|
|
||||||
C/imageViewer/imageviewer
|
|
||||||
C/lichess_random_engine/random_engine
|
|
||||||
C/lichess_random_engine/perft
|
|
||||||
C/misc/generating_words
|
|
||||||
C/misc/randomJPG/generate_images
|
|
||||||
C/misc/split/split
|
|
||||||
C/opening_learner/opening_learner
|
|
||||||
C/scrapeWebsite/scrape
|
|
||||||
C/tests/polish_letters
|
|
||||||
C/vocabulary_curve/vocabulary_curve
|
|
||||||
C/websocketServer/websocket_server
|
|
||||||
CPP/miscelanious/brydz/brydz
|
|
||||||
CPP/miscelanious/calculateShotsDarts/darts
|
|
||||||
CPP/miscelanious/findIntegerPercentageValue/find_percentage
|
|
||||||
CPP/miscelanious/howManyValidISBNNumbersAreThere/isbn_counter
|
|
||||||
CPP/miscelanious/howOftenDoesCharOccur
|
|
||||||
CPP/miscelanious/quickchallenges
|
|
||||||
CPP/miscelanious/reverseString
|
|
||||||
CPP/miscelanious/solveQuadraticEquation
|
|
||||||
CPP/miscelanious/markovChainGenerator/markov
|
|
||||||
CPP/miscelanious/mutiplicationWithoutStar/multiplication
|
|
||||||
CPP/miscelanious/Pi/pi
|
|
||||||
CPP/miscelanious/randomDevice/random_device_demo
|
|
||||||
CPP/miscelanious/tictactoe/tictactoe
|
|
||||||
CPP/miscelanious/tierListConverter/tier_list_converter
|
|
||||||
CPP/miscelanious/xGoesTo0/xgoes
|
|
||||||
CPP/miscelanious/yousuckatcards/a.out
|
|
||||||
CPP/miscelanious/yousuckatcards/Bernouli/bernouli
|
|
||||||
CPP/miscelanious/yousuckatcards/Bernouli/test
|
|
||||||
CPP/tests/division_test
|
|
||||||
|
|
||||||
# Screen locker workout log
|
# Screen locker workout log
|
||||||
python_pkg/screen_locker/workout_log.json
|
python_pkg/screen_locker/workout_log.json
|
||||||
python_pkg/music_gen/output/
|
|
||||||
|
|
||||||
# Screen locker state files
|
# Screen locker state files
|
||||||
python_pkg/screen_locker/sick_day_state.json
|
python_pkg/screen_locker/sick_day_state.json
|
||||||
@ -394,45 +342,16 @@ python_pkg/geo_cache/
|
|||||||
python_pkg/*/.venv/
|
python_pkg/*/.venv/
|
||||||
python_pkg/*/cache/
|
python_pkg/*/cache/
|
||||||
|
|
||||||
# HTTP cat image cache
|
|
||||||
python_pkg/download_cats/http_cat_cache/
|
|
||||||
|
|
||||||
# Large geojson files that can be downloaded
|
|
||||||
python_pkg/anki_decks/warsaw_districts/warszawa-dzielnice.geojson
|
|
||||||
|
|
||||||
# Wikipedia cache (can be refreshed)
|
|
||||||
python_pkg/anki_decks/polish_license_plates/.wikipedia_cache/
|
|
||||||
python_pkg/cinema_planner/pasted_content.txt
|
|
||||||
|
|
||||||
# FVM Version Cache
|
# FVM Version Cache
|
||||||
.fvm/
|
.fvm/
|
||||||
CPP/miscelanious/howManyValidISBNNumbersAreThere/ISBN.txt
|
|
||||||
|
|
||||||
# Focus mode secrets (contains home GPS coordinates)
|
# Focus mode secrets (contains home GPS coordinates)
|
||||||
phone_focus_mode/config_secrets.sh
|
phone_focus_mode/config_secrets.sh
|
||||||
|
|
||||||
# Generated output files
|
# Generated output files
|
||||||
out.txt
|
out.txt
|
||||||
cinema_plan_*.txt
|
|
||||||
|
|
||||||
# Binary images moved to external storage (testsAndMisc_binaries/)
|
|
||||||
python_pkg/praca_magisterska_video/images/
|
|
||||||
|
|
||||||
# Build output symlinks (point to ../testsAndMisc_builds/)
|
# Build output symlinks (point to ../testsAndMisc_builds/)
|
||||||
# Patterns without trailing / to match symlinks, not just directories
|
# Patterns without trailing / to match symlinks, not just directories
|
||||||
pomodoro_app/build
|
|
||||||
horatio/horatio_app/build
|
|
||||||
sonic_pi/build
|
|
||||||
CPP/mini_browser/build
|
|
||||||
phone_focus_mode/focus_status_app/build
|
phone_focus_mode/focus_status_app/build
|
||||||
phone_focus_mode/focus_status_app/debug.keystore
|
phone_focus_mode/focus_status_app/debug.keystore
|
||||||
pomodoro_app/.dart_tool
|
|
||||||
horatio/horatio_app/.dart_tool
|
|
||||||
horatio/horatio_core/.dart_tool
|
|
||||||
|
|
||||||
# Web icon symlinks (point to ../testsAndMisc_binaries/horatio_app_web_icons/)
|
|
||||||
horatio/horatio_app/web/favicon.png
|
|
||||||
horatio/horatio_app/web/icons/Icon-192.png
|
|
||||||
horatio/horatio_app/web/icons/Icon-512.png
|
|
||||||
horatio/horatio_app/web/icons/Icon-maskable-192.png
|
|
||||||
horatio/horatio_app/web/icons/Icon-maskable-512.png
|
|
||||||
|
|||||||
@ -47,7 +47,6 @@ repos:
|
|||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
- id: name-tests-test
|
- id: name-tests-test
|
||||||
args: [--pytest-test-first]
|
args: [--pytest-test-first]
|
||||||
exclude: python_pkg/word_frequency/tests/_translator_helpers\.py
|
|
||||||
- id: check-ast
|
- id: check-ast
|
||||||
- id: check-builtin-literals
|
- id: check-builtin-literals
|
||||||
- id: check-docstring-first
|
- id: check-docstring-first
|
||||||
@ -165,9 +164,6 @@ repos:
|
|||||||
(?x)^(
|
(?x)^(
|
||||||
Bash/.*|
|
Bash/.*|
|
||||||
\.venv/.*|
|
\.venv/.*|
|
||||||
python_pkg/music_gen/.*|
|
|
||||||
python_pkg/praca_magisterska_video/.*|
|
|
||||||
pomodoro_app/tools/.*|
|
|
||||||
linux_configuration/scripts/misc/testsAndMisc-bash/tools/.*
|
linux_configuration/scripts/misc/testsAndMisc-bash/tools/.*
|
||||||
)$
|
)$
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
@ -349,7 +345,6 @@ repos:
|
|||||||
entry: bash -c 'printf "%s\0" "$@" | xargs -0 -n 40 shellcheck --severity=warning' --
|
entry: bash -c 'printf "%s\0" "$@" | xargs -0 -n 40 shellcheck --severity=warning' --
|
||||||
language: system
|
language: system
|
||||||
types: [shell]
|
types: [shell]
|
||||||
exclude: ^pomodoro_app/
|
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# CLANG-FORMAT - C/C++ code formatting
|
# CLANG-FORMAT - C/C++ code formatting
|
||||||
@ -370,7 +365,6 @@ repos:
|
|||||||
entry: cppcheck
|
entry: cppcheck
|
||||||
language: system
|
language: system
|
||||||
types_or: [c, c++]
|
types_or: [c, c++]
|
||||||
exclude: ^(pomodoro_app/|horatio/)
|
|
||||||
args:
|
args:
|
||||||
- --enable=warning,portability
|
- --enable=warning,portability
|
||||||
- --force
|
- --force
|
||||||
@ -399,18 +393,6 @@ repos:
|
|||||||
- --quiet
|
- --quiet
|
||||||
- --columns
|
- --columns
|
||||||
|
|
||||||
# ===========================================================================
|
|
||||||
# ESLINT - TypeScript/JavaScript linting
|
|
||||||
# ===========================================================================
|
|
||||||
- repo: local
|
|
||||||
hooks:
|
|
||||||
- id: eslint
|
|
||||||
name: eslint
|
|
||||||
entry: npx eslint --no-warn-ignored
|
|
||||||
language: system
|
|
||||||
types_or: [ts, tsx]
|
|
||||||
files: ^TS/
|
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# CHECK C/C++ BUILD FILES - Ensure every C/C++ dir has Makefile and run.sh
|
# CHECK C/C++ BUILD FILES - Ensure every C/C++ dir has Makefile and run.sh
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
@ -421,7 +403,6 @@ repos:
|
|||||||
entry: scripts/check_c_cpp_build_files.sh
|
entry: scripts/check_c_cpp_build_files.sh
|
||||||
language: script
|
language: script
|
||||||
types_or: [c, c++]
|
types_or: [c, c++]
|
||||||
exclude: ^(CPP/mini_browser/|horatio/)
|
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# CHECK PYTHON LOCATION - All Python files must be under python_pkg/
|
# CHECK PYTHON LOCATION - All Python files must be under python_pkg/
|
||||||
@ -466,29 +447,3 @@ repos:
|
|||||||
# - id: commitizen
|
# - id: commitizen
|
||||||
# - id: commitizen-branch
|
# - id: commitizen-branch
|
||||||
# stages: [push]
|
# stages: [push]
|
||||||
|
|
||||||
# ===========================================================================
|
|
||||||
# POMODORO APP - Flutter analyze + test (push only)
|
|
||||||
# ===========================================================================
|
|
||||||
- repo: local
|
|
||||||
hooks:
|
|
||||||
- id: pomodoro-app-flutter
|
|
||||||
name: pomodoro_app flutter analyze & test
|
|
||||||
entry: bash -c 'cd pomodoro_app && flutter pub get --enforce-lockfile && flutter analyze && flutter test'
|
|
||||||
language: system
|
|
||||||
files: ^pomodoro_app/
|
|
||||||
pass_filenames: false
|
|
||||||
stages: [pre-push]
|
|
||||||
|
|
||||||
# ===========================================================================
|
|
||||||
# HORATIO - Dart/Flutter tests with coverage enforcement (push only)
|
|
||||||
# ===========================================================================
|
|
||||||
- repo: local
|
|
||||||
hooks:
|
|
||||||
- id: horatio-tests
|
|
||||||
name: horatio test coverage
|
|
||||||
entry: bash -c 'cd horatio && bash run.sh test'
|
|
||||||
language: system
|
|
||||||
files: ^horatio/
|
|
||||||
stages: [pre-push]
|
|
||||||
pass_filenames: false
|
|
||||||
|
|||||||
18
.vscode/settings.json
vendored
18
.vscode/settings.json
vendored
@ -21,16 +21,7 @@
|
|||||||
"**/.venv": true,
|
"**/.venv": true,
|
||||||
".ruff_cache": true,
|
".ruff_cache": true,
|
||||||
".mypy_cache": true,
|
".mypy_cache": true,
|
||||||
"C/misc/randomJPG": true,
|
|
||||||
"pomodoro_app/build": true,
|
|
||||||
"horatio/horatio_app/build": true,
|
|
||||||
"sonic_pi/build": true,
|
|
||||||
"CPP/mini_browser/build": true,
|
|
||||||
"**/.dart_tool": true,
|
"**/.dart_tool": true,
|
||||||
"python_pkg/anki_decks/**/preview_images": true,
|
|
||||||
"python_pkg/download_cats/http_cat_cache": true,
|
|
||||||
"python_pkg/articles/uploads": true,
|
|
||||||
"python_pkg/praca_magisterska_video/images": true,
|
|
||||||
"**/generated_images_*": true,
|
"**/generated_images_*": true,
|
||||||
"**/coverage": true
|
"**/coverage": true
|
||||||
},
|
},
|
||||||
@ -50,11 +41,7 @@
|
|||||||
"**/*.sqlite": true,
|
"**/*.sqlite": true,
|
||||||
"**/.dart_tool": true,
|
"**/.dart_tool": true,
|
||||||
"**/ephemeral": true,
|
"**/ephemeral": true,
|
||||||
"**/node_modules": true,
|
"**/node_modules": true
|
||||||
"python_pkg/anki_decks": true,
|
|
||||||
"python_pkg/praca_magisterska_video": true,
|
|
||||||
"python_pkg/articles/uploads": true,
|
|
||||||
"python_pkg/download_cats/http_cat_cache": true
|
|
||||||
},
|
},
|
||||||
"coverage-gutters.coverageFileNames": [
|
"coverage-gutters.coverageFileNames": [
|
||||||
"lcov.info",
|
"lcov.info",
|
||||||
@ -67,6 +54,5 @@
|
|||||||
],
|
],
|
||||||
"python.testing.pytestEnabled": true,
|
"python.testing.pytestEnabled": true,
|
||||||
"python.testing.pytestArgs": ["python_pkg"],
|
"python.testing.pytestArgs": ["python_pkg"],
|
||||||
"python-envs.alwaysUseUv": true,
|
"python-envs.alwaysUseUv": true
|
||||||
"cmake.sourceDirectory": "/home/kuhy/testsAndMisc/pomodoro_app/linux"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,47 +0,0 @@
|
|||||||
CC := gcc
|
|
||||||
CFLAGS := -O2 -Wall -Wextra -std=c11 -D_DEFAULT_SOURCE
|
|
||||||
LDFLAGS := -lm
|
|
||||||
|
|
||||||
SRC := main.c physics.c
|
|
||||||
BIN := 1dvelocitysimulator
|
|
||||||
|
|
||||||
TEST_SRC := test_physics.c physics.c
|
|
||||||
TEST_BIN := test_physics
|
|
||||||
|
|
||||||
COV_CFLAGS := -Wall -Wextra -std=c11 -D_DEFAULT_SOURCE -DTESTING --coverage -g -O0
|
|
||||||
COV_LDFLAGS := -lm
|
|
||||||
|
|
||||||
all: $(BIN)
|
|
||||||
|
|
||||||
$(BIN): $(SRC) physics.h
|
|
||||||
$(CC) $(CFLAGS) -o $@ $(SRC) $(LDFLAGS)
|
|
||||||
|
|
||||||
run: $(BIN)
|
|
||||||
./$(BIN)
|
|
||||||
|
|
||||||
test: $(TEST_BIN)
|
|
||||||
./$(TEST_BIN)
|
|
||||||
|
|
||||||
$(TEST_BIN): $(TEST_SRC) physics.h
|
|
||||||
$(CC) $(CFLAGS) -DTESTING -o $@ $(TEST_SRC) $(LDFLAGS)
|
|
||||||
|
|
||||||
coverage:
|
|
||||||
$(CC) $(COV_CFLAGS) -o $(TEST_BIN) $(TEST_SRC) $(COV_LDFLAGS)
|
|
||||||
./$(TEST_BIN)
|
|
||||||
lcov --capture --directory . --output-file coverage.info --rc branch_coverage=1
|
|
||||||
lcov --remove coverage.info '/usr/*' --output-file coverage.info \
|
|
||||||
--rc branch_coverage=1 --ignore-errors unused
|
|
||||||
@LINE_PCT=$$(lcov --summary coverage.info 2>&1 | grep -oP 'lines\.*:\s*\K[0-9.]+'); \
|
|
||||||
echo "Line coverage: $${LINE_PCT}%"; \
|
|
||||||
if [ "$$(echo "$${LINE_PCT} < 100.0" | bc -l)" = "1" ]; then \
|
|
||||||
echo "FAIL: Line coverage $${LINE_PCT}% is below 100%"; \
|
|
||||||
exit 1; \
|
|
||||||
else \
|
|
||||||
echo "OK: 100% line coverage achieved"; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(BIN) $(TEST_BIN) *.gcda *.gcno *.gcov coverage.info
|
|
||||||
rm -rf coverage_html
|
|
||||||
|
|
||||||
.PHONY: all run test coverage clean
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
#include "physics.h"
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
int position = 0, acceleration = -1;
|
|
||||||
int *Pacceleration = &acceleration;
|
|
||||||
unsigned int time = 0;
|
|
||||||
unsigned int *Ptime = &time;
|
|
||||||
moveUntillOutOfLine(position, Ptime);
|
|
||||||
// moveUntillOutOfVelocity(position, Pacceleration, Ptime);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@ -1,160 +0,0 @@
|
|||||||
#include "physics.h"
|
|
||||||
|
|
||||||
#include <math.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
void C()
|
|
||||||
{
|
|
||||||
printf("\nCheck\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void printAcceleration(int acceleration)
|
|
||||||
{
|
|
||||||
printf("The value of acceleration is: %d\n", acceleration);
|
|
||||||
PAUSE();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pauseSystem() { PAUSE(); }
|
|
||||||
|
|
||||||
void clearScreen()
|
|
||||||
{
|
|
||||||
CLEAR_SCREEN();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pauseForASecond()
|
|
||||||
{
|
|
||||||
SLEEP_MS(1000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pauseForGivenTime(float given_time)
|
|
||||||
{
|
|
||||||
SLEEP_MS((unsigned int)fabs((double)given_time * 1000));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float calculateVelocity(float starting_velocity, unsigned int physics_time, int *acceleration)
|
|
||||||
{
|
|
||||||
// cppcheck-suppress nullPointer
|
|
||||||
return (*acceleration) * physics_time + starting_velocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
int calculateDisplacement(float starting_velocity, int *acceleration, unsigned int physics_time)
|
|
||||||
{
|
|
||||||
// cppcheck-suppress nullPointer
|
|
||||||
// cppcheck-suppress ctunullpointer
|
|
||||||
return starting_velocity * physics_time + ((1 / 2) * (*acceleration) * (physics_time ^ 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
void printXPosition(int position)
|
|
||||||
{
|
|
||||||
printf("\nx position is: %d\n", position);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void printClock(unsigned int *time)
|
|
||||||
{
|
|
||||||
printf("%u seconds passed\n", *time);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float calculateStopTime(float velocity) { return 1 / velocity; }
|
|
||||||
|
|
||||||
void printLine(int position)
|
|
||||||
{
|
|
||||||
clearScreen();
|
|
||||||
for (int i = -(LINE_LENGTH / 2); i < LINE_LENGTH / 2; i++)
|
|
||||||
{
|
|
||||||
if (i == position)
|
|
||||||
printf("x");
|
|
||||||
else
|
|
||||||
printf("-");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void printVelocity(float velocity)
|
|
||||||
{
|
|
||||||
printf("Velocity is: %f\n", velocity);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int calculateTimePassed(float velocity)
|
|
||||||
{
|
|
||||||
if (velocity >= 1 || velocity <= -1)
|
|
||||||
return 1;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
printf("Time passed is: %f\n", fabs(1 / velocity));
|
|
||||||
return fabs(1 / velocity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void printAllInfo(int position, unsigned int *time, float *velocity)
|
|
||||||
{
|
|
||||||
pauseForGivenTime(calculateStopTime(*velocity));
|
|
||||||
printLine(position);
|
|
||||||
printXPosition(position);
|
|
||||||
*time += calculateTimePassed(*velocity);
|
|
||||||
printClock(time);
|
|
||||||
printVelocity(*velocity);
|
|
||||||
// pauseForASecond();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float chooseVelocity()
|
|
||||||
{
|
|
||||||
float velocity;
|
|
||||||
printf("Write velocity of the object in m / s: ");
|
|
||||||
scanf("%f", &velocity);
|
|
||||||
return velocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
int chooseAcceleration()
|
|
||||||
{
|
|
||||||
int acceleration;
|
|
||||||
printf("Choose acceleration of the object in m / (s ^ 2):");
|
|
||||||
scanf("%d", &acceleration);
|
|
||||||
return acceleration;
|
|
||||||
}
|
|
||||||
|
|
||||||
int outOfLine(int position)
|
|
||||||
{
|
|
||||||
if ((position < LINE_LENGTH / 2) && (position > -1 * (LINE_LENGTH / 2)))
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void moveUntillOutOfLine(int position, unsigned int *time)
|
|
||||||
{
|
|
||||||
while (!outOfLine(position))
|
|
||||||
{
|
|
||||||
float velocity = chooseVelocity();
|
|
||||||
float *Pvelocity = &velocity;
|
|
||||||
position += calculateDisplacement(velocity, 0, 1);
|
|
||||||
printAllInfo(position, time, Pvelocity);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void moveUntillOutOfVelocity(int position, int *acceleration, unsigned int *time)
|
|
||||||
{
|
|
||||||
float velocity = 0;
|
|
||||||
float *Pvelocity = &velocity;
|
|
||||||
while (!outOfLine(position))
|
|
||||||
{
|
|
||||||
position += calculateDisplacement(velocity, acceleration, 1);
|
|
||||||
printXPosition(position);
|
|
||||||
pauseSystem();
|
|
||||||
velocity = calculateVelocity(velocity, 1, acceleration);
|
|
||||||
printAllInfo(position, time, Pvelocity);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
#ifndef PHYSICS_H
|
|
||||||
#define PHYSICS_H
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#define SLEEP_MS(ms) Sleep(ms)
|
|
||||||
#define CLEAR_SCREEN() system("CLS")
|
|
||||||
#define PAUSE() \
|
|
||||||
do \
|
|
||||||
{ \
|
|
||||||
printf("Press Enter to continue..."); \
|
|
||||||
getchar(); \
|
|
||||||
} while (0)
|
|
||||||
#else
|
|
||||||
#include <unistd.h>
|
|
||||||
#ifdef TESTING
|
|
||||||
#define SLEEP_MS(ms) ((void)0)
|
|
||||||
#define CLEAR_SCREEN() ((void)0)
|
|
||||||
#define PAUSE() ((void)0)
|
|
||||||
#else
|
|
||||||
#define SLEEP_MS(ms) usleep((ms) * 1000U)
|
|
||||||
#define CLEAR_SCREEN() system("clear")
|
|
||||||
#define PAUSE() \
|
|
||||||
do \
|
|
||||||
{ \
|
|
||||||
printf("Press Enter to continue..."); \
|
|
||||||
getchar(); \
|
|
||||||
} while (0)
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define LINE_LENGTH 100
|
|
||||||
|
|
||||||
void C(void);
|
|
||||||
void printAcceleration(int acceleration);
|
|
||||||
void pauseSystem(void);
|
|
||||||
void clearScreen(void);
|
|
||||||
void pauseForASecond(void);
|
|
||||||
void pauseForGivenTime(float given_time);
|
|
||||||
float calculateVelocity(float starting_velocity, unsigned int physics_time, int *acceleration);
|
|
||||||
int calculateDisplacement(float starting_velocity, int *acceleration, unsigned int physics_time);
|
|
||||||
void printXPosition(int position);
|
|
||||||
void printClock(unsigned int *time);
|
|
||||||
float calculateStopTime(float velocity);
|
|
||||||
void printLine(int position);
|
|
||||||
void printVelocity(float velocity);
|
|
||||||
int calculateTimePassed(float velocity);
|
|
||||||
void printAllInfo(int position, unsigned int *time, float *velocity);
|
|
||||||
float chooseVelocity(void);
|
|
||||||
int chooseAcceleration(void);
|
|
||||||
int outOfLine(int position);
|
|
||||||
void moveUntillOutOfLine(int position, unsigned int *time);
|
|
||||||
void moveUntillOutOfVelocity(int position, int *acceleration, unsigned int *time);
|
|
||||||
|
|
||||||
#endif /* PHYSICS_H */
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
make
|
|
||||||
./1dvelocitysimulator
|
|
||||||
@ -1,468 +0,0 @@
|
|||||||
#include <assert.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "physics.h"
|
|
||||||
|
|
||||||
static void test_calculateVelocity(void)
|
|
||||||
{
|
|
||||||
int accel = 2;
|
|
||||||
float v = calculateVelocity(5.0f, 3, &accel);
|
|
||||||
assert(fabsf(v - 11.0f) < 0.001f);
|
|
||||||
|
|
||||||
/* acceleration=0: velocity unchanged */
|
|
||||||
int accel2 = 0;
|
|
||||||
float v2 = calculateVelocity(10.0f, 5, &accel2);
|
|
||||||
assert(fabsf(v2 - 10.0f) < 0.001f);
|
|
||||||
|
|
||||||
int accel3 = 0;
|
|
||||||
float v3 = calculateVelocity(3.0f, 10, &accel3);
|
|
||||||
assert(fabsf(v3 - 3.0f) < 0.001f);
|
|
||||||
|
|
||||||
/* time=0: velocity equals starting velocity regardless of accel */
|
|
||||||
int accel4 = 100;
|
|
||||||
float v4 = calculateVelocity(7.0f, 0, &accel4);
|
|
||||||
assert(fabsf(v4 - 7.0f) < 0.001f);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_calculateDisplacement(void)
|
|
||||||
{
|
|
||||||
int accel = 2;
|
|
||||||
int d = calculateDisplacement(5.0f, &accel, 3);
|
|
||||||
/* With integer division (1/2)==0, result is starting_velocity * time + 0 */
|
|
||||||
assert(d == 15);
|
|
||||||
|
|
||||||
int accel2 = 0;
|
|
||||||
int d2 = calculateDisplacement(0.0f, &accel2, 10);
|
|
||||||
assert(d2 == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_calculateStopTime(void)
|
|
||||||
{
|
|
||||||
float t = calculateStopTime(2.0f);
|
|
||||||
assert(fabsf(t - 0.5f) < 0.001f);
|
|
||||||
|
|
||||||
float t2 = calculateStopTime(0.5f);
|
|
||||||
assert(fabsf(t2 - 2.0f) < 0.001f);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_calculateTimePassed_fast(void)
|
|
||||||
{
|
|
||||||
int t = calculateTimePassed(2.0f);
|
|
||||||
assert(t == 1);
|
|
||||||
|
|
||||||
int t2 = calculateTimePassed(-5.0f);
|
|
||||||
assert(t2 == 1);
|
|
||||||
|
|
||||||
int t3 = calculateTimePassed(1.0f);
|
|
||||||
assert(t3 == 1);
|
|
||||||
|
|
||||||
int t4 = calculateTimePassed(-1.0f);
|
|
||||||
assert(t4 == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_calculateTimePassed_slow(void)
|
|
||||||
{
|
|
||||||
/* velocity between -1 and 1 (exclusive) takes the else branch */
|
|
||||||
int t = calculateTimePassed(0.5f);
|
|
||||||
assert(t == (int)fabsf(1.0f / 0.5f));
|
|
||||||
|
|
||||||
int t2 = calculateTimePassed(0.25f);
|
|
||||||
assert(t2 == (int)fabsf(1.0f / 0.25f));
|
|
||||||
|
|
||||||
int t3 = calculateTimePassed(-0.5f);
|
|
||||||
assert(t3 == (int)fabsf(1.0f / -0.5f));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_outOfLine(void)
|
|
||||||
{
|
|
||||||
assert(outOfLine(0) == 0);
|
|
||||||
assert(outOfLine(10) == 0);
|
|
||||||
assert(outOfLine(-10) == 0);
|
|
||||||
assert(outOfLine(49) == 0);
|
|
||||||
assert(outOfLine(-49) == 0);
|
|
||||||
|
|
||||||
/* at boundary and beyond */
|
|
||||||
assert(outOfLine(50) == 1);
|
|
||||||
assert(outOfLine(-50) == 1);
|
|
||||||
assert(outOfLine(100) == 1);
|
|
||||||
assert(outOfLine(-100) == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_C_function(void)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
FILE *_redir = freopen("/dev/null", "w", stdout);
|
|
||||||
assert(_redir != NULL);
|
|
||||||
(void)_redir;
|
|
||||||
}
|
|
||||||
C();
|
|
||||||
{
|
|
||||||
FILE *_restore = freopen("/dev/tty", "w", stdout);
|
|
||||||
assert(_restore != NULL);
|
|
||||||
(void)_restore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_printAcceleration(void)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
FILE *_redir = freopen("/dev/null", "w", stdout);
|
|
||||||
assert(_redir != NULL);
|
|
||||||
(void)_redir;
|
|
||||||
}
|
|
||||||
printAcceleration(5);
|
|
||||||
printAcceleration(-3);
|
|
||||||
{
|
|
||||||
FILE *_restore = freopen("/dev/tty", "w", stdout);
|
|
||||||
assert(_restore != NULL);
|
|
||||||
(void)_restore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_pauseSystem(void)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
FILE *_redir = freopen("/dev/null", "w", stdout);
|
|
||||||
assert(_redir != NULL);
|
|
||||||
(void)_redir;
|
|
||||||
}
|
|
||||||
pauseSystem();
|
|
||||||
{
|
|
||||||
FILE *_restore = freopen("/dev/tty", "w", stdout);
|
|
||||||
assert(_restore != NULL);
|
|
||||||
(void)_restore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_clearScreen(void) { clearScreen(); }
|
|
||||||
|
|
||||||
static void test_pauseForASecond(void) { pauseForASecond(); }
|
|
||||||
|
|
||||||
static void test_pauseForGivenTime(void)
|
|
||||||
{
|
|
||||||
pauseForGivenTime(0.5f);
|
|
||||||
pauseForGivenTime(-0.5f);
|
|
||||||
pauseForGivenTime(0.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_printXPosition(void)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
FILE *_redir = freopen("/dev/null", "w", stdout);
|
|
||||||
assert(_redir != NULL);
|
|
||||||
(void)_redir;
|
|
||||||
}
|
|
||||||
printXPosition(0);
|
|
||||||
printXPosition(42);
|
|
||||||
printXPosition(-10);
|
|
||||||
{
|
|
||||||
FILE *_restore = freopen("/dev/tty", "w", stdout);
|
|
||||||
assert(_restore != NULL);
|
|
||||||
(void)_restore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_printClock(void)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
FILE *_redir = freopen("/dev/null", "w", stdout);
|
|
||||||
assert(_redir != NULL);
|
|
||||||
(void)_redir;
|
|
||||||
}
|
|
||||||
unsigned int t = 10;
|
|
||||||
printClock(&t);
|
|
||||||
t = 0;
|
|
||||||
printClock(&t);
|
|
||||||
{
|
|
||||||
FILE *_restore = freopen("/dev/tty", "w", stdout);
|
|
||||||
assert(_restore != NULL);
|
|
||||||
(void)_restore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_printVelocity(void)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
FILE *_redir = freopen("/dev/null", "w", stdout);
|
|
||||||
assert(_redir != NULL);
|
|
||||||
(void)_redir;
|
|
||||||
}
|
|
||||||
printVelocity(3.14f);
|
|
||||||
printVelocity(-1.0f);
|
|
||||||
{
|
|
||||||
FILE *_restore = freopen("/dev/tty", "w", stdout);
|
|
||||||
assert(_restore != NULL);
|
|
||||||
(void)_restore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_printLine(void)
|
|
||||||
{
|
|
||||||
/* Capture output to a temp file to verify content */
|
|
||||||
FILE *tmp = tmpfile();
|
|
||||||
assert(tmp != NULL);
|
|
||||||
int fd = fileno(tmp);
|
|
||||||
FILE *cap = fdopen(fd, "w+");
|
|
||||||
/* Redirect stdout to tmp */
|
|
||||||
FILE *saved = stdout;
|
|
||||||
stdout = cap;
|
|
||||||
|
|
||||||
printLine(0);
|
|
||||||
fflush(stdout);
|
|
||||||
|
|
||||||
stdout = saved;
|
|
||||||
|
|
||||||
/* Read back and verify "x" at the correct position */
|
|
||||||
fseek(cap, 0, SEEK_END);
|
|
||||||
long len = ftell(cap);
|
|
||||||
fseek(cap, 0, SEEK_SET);
|
|
||||||
char *buf = malloc(len + 1);
|
|
||||||
assert(buf != NULL);
|
|
||||||
fread(buf, 1, len, cap);
|
|
||||||
buf[len] = '\0';
|
|
||||||
|
|
||||||
/* The output is LINE_LENGTH characters. Position 0 maps to index 50 */
|
|
||||||
assert(len == LINE_LENGTH);
|
|
||||||
assert(buf[50] == 'x');
|
|
||||||
for (int i = 0; i < LINE_LENGTH; i++)
|
|
||||||
{
|
|
||||||
if (i != 50)
|
|
||||||
assert(buf[i] == '-');
|
|
||||||
}
|
|
||||||
free(buf);
|
|
||||||
fclose(cap);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_printLine_edge(void)
|
|
||||||
{
|
|
||||||
FILE *saved = stdout;
|
|
||||||
FILE *tmp = tmpfile();
|
|
||||||
assert(tmp != NULL);
|
|
||||||
stdout = tmp;
|
|
||||||
printLine(-50);
|
|
||||||
fflush(stdout);
|
|
||||||
stdout = saved;
|
|
||||||
|
|
||||||
fseek(tmp, 0, SEEK_END);
|
|
||||||
long len = ftell(tmp);
|
|
||||||
fseek(tmp, 0, SEEK_SET);
|
|
||||||
char *buf = malloc(len + 1);
|
|
||||||
assert(buf != NULL);
|
|
||||||
fread(buf, 1, len, tmp);
|
|
||||||
buf[len] = '\0';
|
|
||||||
|
|
||||||
/* position -50 maps to index 0 */
|
|
||||||
assert(buf[0] == 'x');
|
|
||||||
free(buf);
|
|
||||||
fclose(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_printAllInfo(void)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
FILE *_redir = freopen("/dev/null", "w", stdout);
|
|
||||||
assert(_redir != NULL);
|
|
||||||
(void)_redir;
|
|
||||||
}
|
|
||||||
unsigned int t = 0;
|
|
||||||
float vel = 2.0f;
|
|
||||||
printAllInfo(0, &t, &vel);
|
|
||||||
assert(t > 0);
|
|
||||||
|
|
||||||
/* slow velocity branch */
|
|
||||||
float vel2 = 0.5f;
|
|
||||||
unsigned int t2 = 0;
|
|
||||||
printAllInfo(10, &t2, &vel2);
|
|
||||||
assert(t2 > 0);
|
|
||||||
{
|
|
||||||
FILE *_restore = freopen("/dev/tty", "w", stdout);
|
|
||||||
assert(_restore != NULL);
|
|
||||||
(void)_restore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_chooseVelocity(void)
|
|
||||||
{
|
|
||||||
/* Redirect stdin to provide input */
|
|
||||||
FILE *tmp_in = tmpfile();
|
|
||||||
assert(tmp_in != NULL);
|
|
||||||
fprintf(tmp_in, "3.5\n");
|
|
||||||
fseek(tmp_in, 0, SEEK_SET);
|
|
||||||
|
|
||||||
FILE *saved_in = stdin;
|
|
||||||
stdin = tmp_in;
|
|
||||||
|
|
||||||
{
|
|
||||||
FILE *_redir = freopen("/dev/null", "w", stdout);
|
|
||||||
assert(_redir != NULL);
|
|
||||||
(void)_redir;
|
|
||||||
}
|
|
||||||
|
|
||||||
float v = chooseVelocity();
|
|
||||||
assert(fabsf(v - 3.5f) < 0.001f);
|
|
||||||
|
|
||||||
stdin = saved_in;
|
|
||||||
fclose(tmp_in);
|
|
||||||
{
|
|
||||||
FILE *_restore = freopen("/dev/tty", "w", stdout);
|
|
||||||
assert(_restore != NULL);
|
|
||||||
(void)_restore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_chooseAcceleration(void)
|
|
||||||
{
|
|
||||||
FILE *tmp_in = tmpfile();
|
|
||||||
assert(tmp_in != NULL);
|
|
||||||
fprintf(tmp_in, "7\n");
|
|
||||||
fseek(tmp_in, 0, SEEK_SET);
|
|
||||||
|
|
||||||
FILE *saved_in = stdin;
|
|
||||||
stdin = tmp_in;
|
|
||||||
|
|
||||||
{
|
|
||||||
FILE *_redir = freopen("/dev/null", "w", stdout);
|
|
||||||
assert(_redir != NULL);
|
|
||||||
(void)_redir;
|
|
||||||
}
|
|
||||||
|
|
||||||
int a = chooseAcceleration();
|
|
||||||
assert(a == 7);
|
|
||||||
|
|
||||||
stdin = saved_in;
|
|
||||||
fclose(tmp_in);
|
|
||||||
{
|
|
||||||
FILE *_restore = freopen("/dev/tty", "w", stdout);
|
|
||||||
assert(_restore != NULL);
|
|
||||||
(void)_restore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_moveUntillOutOfLine_already_out(void)
|
|
||||||
{
|
|
||||||
/* Position already out of line: while loop body never executes */
|
|
||||||
{
|
|
||||||
FILE *_redir = freopen("/dev/null", "w", stdout);
|
|
||||||
assert(_redir != NULL);
|
|
||||||
(void)_redir;
|
|
||||||
}
|
|
||||||
unsigned int t = 0;
|
|
||||||
moveUntillOutOfLine(999, &t);
|
|
||||||
assert(t == 0);
|
|
||||||
{
|
|
||||||
FILE *_restore = freopen("/dev/tty", "w", stdout);
|
|
||||||
assert(_restore != NULL);
|
|
||||||
(void)_restore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_moveUntillOutOfLine_exits(void)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Position starts in-line (0). Feed a large velocity via stdin so
|
|
||||||
* calculateDisplacement moves position out of line in one step.
|
|
||||||
* chooseVelocity reads a float; we feed "100\n".
|
|
||||||
*/
|
|
||||||
FILE *tmp_in = tmpfile();
|
|
||||||
assert(tmp_in != NULL);
|
|
||||||
fprintf(tmp_in, "100\n");
|
|
||||||
fseek(tmp_in, 0, SEEK_SET);
|
|
||||||
|
|
||||||
FILE *saved_in = stdin;
|
|
||||||
stdin = tmp_in;
|
|
||||||
|
|
||||||
{
|
|
||||||
FILE *_redir = freopen("/dev/null", "w", stdout);
|
|
||||||
assert(_redir != NULL);
|
|
||||||
(void)_redir;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int t = 0;
|
|
||||||
moveUntillOutOfLine(0, &t);
|
|
||||||
|
|
||||||
stdin = saved_in;
|
|
||||||
fclose(tmp_in);
|
|
||||||
{
|
|
||||||
FILE *_restore = freopen("/dev/tty", "w", stdout);
|
|
||||||
assert(_restore != NULL);
|
|
||||||
(void)_restore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_moveUntillOutOfVelocity_already_out(void)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
FILE *_redir = freopen("/dev/null", "w", stdout);
|
|
||||||
assert(_redir != NULL);
|
|
||||||
(void)_redir;
|
|
||||||
}
|
|
||||||
int accel = 5;
|
|
||||||
unsigned int t = 0;
|
|
||||||
moveUntillOutOfVelocity(999, &accel, &t);
|
|
||||||
assert(t == 0);
|
|
||||||
{
|
|
||||||
FILE *_restore = freopen("/dev/tty", "w", stdout);
|
|
||||||
assert(_restore != NULL);
|
|
||||||
(void)_restore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_moveUntillOutOfVelocity_runs(void)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Start at position 49 (near boundary) with positive acceleration.
|
|
||||||
* velocity starts at 0, first iteration: displacement = 0*1 + 0 = 0, position
|
|
||||||
* stays 49. velocity becomes accel*1+0 = 10. Second iteration: displacement =
|
|
||||||
* 10*1 + 0 = 10, position = 59 -> out of line. We need at most a few iterations.
|
|
||||||
* Since chooseVelocity/chooseAcceleration are NOT called in this function, no
|
|
||||||
* stdin redirect needed.
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
FILE *_redir = freopen("/dev/null", "w", stdout);
|
|
||||||
assert(_redir != NULL);
|
|
||||||
(void)_redir;
|
|
||||||
}
|
|
||||||
int accel = 10;
|
|
||||||
unsigned int t = 0;
|
|
||||||
moveUntillOutOfVelocity(49, &accel, &t);
|
|
||||||
assert(t > 0);
|
|
||||||
{
|
|
||||||
FILE *_restore = freopen("/dev/tty", "w", stdout);
|
|
||||||
assert(_restore != NULL);
|
|
||||||
(void)_restore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
test_calculateVelocity();
|
|
||||||
test_calculateDisplacement();
|
|
||||||
test_calculateStopTime();
|
|
||||||
test_calculateTimePassed_fast();
|
|
||||||
test_calculateTimePassed_slow();
|
|
||||||
test_outOfLine();
|
|
||||||
test_C_function();
|
|
||||||
test_printAcceleration();
|
|
||||||
test_pauseSystem();
|
|
||||||
test_clearScreen();
|
|
||||||
test_pauseForASecond();
|
|
||||||
test_pauseForGivenTime();
|
|
||||||
test_printXPosition();
|
|
||||||
test_printClock();
|
|
||||||
test_printVelocity();
|
|
||||||
test_printLine();
|
|
||||||
test_printLine_edge();
|
|
||||||
test_printAllInfo();
|
|
||||||
test_chooseVelocity();
|
|
||||||
test_chooseAcceleration();
|
|
||||||
test_moveUntillOutOfLine_already_out();
|
|
||||||
test_moveUntillOutOfLine_exits();
|
|
||||||
test_moveUntillOutOfVelocity_already_out();
|
|
||||||
test_moveUntillOutOfVelocity_runs();
|
|
||||||
|
|
||||||
printf("All tests passed!\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
# Minimal Makefile for the simple OpenGL FPS demo
|
|
||||||
# Targets: build (default), run, clean
|
|
||||||
|
|
||||||
CC := gcc
|
|
||||||
CFLAGS := -O2 -Wall -Wextra -Wno-missing-field-initializers -std=c11 $(shell pkg-config --cflags sdl2 2>/dev/null)
|
|
||||||
LDFLAGS := -lglut -lGLU -lGL -lm $(shell pkg-config --libs sdl2 2>/dev/null)
|
|
||||||
|
|
||||||
SRC := main.c
|
|
||||||
BIN := fps_demo
|
|
||||||
|
|
||||||
all: $(BIN)
|
|
||||||
|
|
||||||
$(BIN): $(SRC)
|
|
||||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
run: $(BIN)
|
|
||||||
./$(BIN)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(BIN)
|
|
||||||
|
|
||||||
.PHONY: all run clean
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
# Simple OpenGL FPS (C + FreeGLUT)
|
|
||||||
|
|
||||||
A tiny first-person demo using legacy OpenGL (compat) and FreeGLUT:
|
|
||||||
|
|
||||||
- Move with WASD, hold Tab or Q to sprint
|
|
||||||
- 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
|
|
||||||
- Press Esc to quit
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
Requires development packages for OpenGL, GLU, FreeGLUT, and SDL2 (for audio).
|
|
||||||
On Debian/Ubuntu:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y build-essential freeglut3-dev libsdl2-dev pkg-config
|
|
||||||
```
|
|
||||||
|
|
||||||
Then build and run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
make -C C/fps
|
|
||||||
make -C C/fps run
|
|
||||||
```
|
|
||||||
|
|
||||||
If your distro uses different package names, install the equivalents of:
|
|
||||||
|
|
||||||
- libgl1, libglu1, freeglut (dev headers)
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- 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.
|
|
||||||
- SDL2 is used only for simple procedurally generated sound effects (shoot, hit, game over).
|
|
||||||
724
C/fps/main.c
724
C/fps/main.c
@ -1,724 +0,0 @@
|
|||||||
// Simple FPS demo using FreeGLUT + legacy OpenGL (compat profile)
|
|
||||||
// - Move: WASD, Shift to sprint, Space to shoot, Esc to quit
|
|
||||||
// - Look: mouse
|
|
||||||
// - Targets: red cubes move toward you; shoot them before they reach you
|
|
||||||
// - Game over when a target reaches you; final score shown; press R to restart
|
|
||||||
|
|
||||||
#include <GL/freeglut_std.h>
|
|
||||||
#include <GL/gl.h>
|
|
||||||
#include <GL/glu.h>
|
|
||||||
#include <SDL2/SDL.h>
|
|
||||||
#include <SDL2/SDL_audio.h>
|
|
||||||
#include <SDL2/SDL_error.h>
|
|
||||||
#include <SDL2/SDL_stdinc.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#ifndef M_PI
|
|
||||||
#define M_PI 3.14159265358979323846
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// -------- Math helpers --------
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
float x, y, z;
|
|
||||||
} vec3;
|
|
||||||
|
|
||||||
static inline vec3 v3(float x, float y, float z)
|
|
||||||
{
|
|
||||||
vec3 v = {x, y, z};
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
static inline vec3 v3_add(vec3 a, vec3 b) { return v3(a.x + b.x, a.y + b.y, a.z + b.z); }
|
|
||||||
static inline vec3 v3_sub(vec3 a, vec3 b) { return v3(a.x - b.x, a.y - b.y, a.z - b.z); }
|
|
||||||
static inline vec3 v3_scale(vec3 a, float s) { return v3(a.x * s, a.y * s, a.z * s); }
|
|
||||||
static inline float v3_dot(vec3 a, vec3 b) { return (a.x * b.x) + (a.y * b.y) + (a.z * b.z); }
|
|
||||||
static inline vec3 v3_cross(vec3 a, vec3 b)
|
|
||||||
{
|
|
||||||
return v3((a.y * b.z) - (a.z * b.y), (a.z * b.x) - (a.x * b.z), (a.x * b.y) - (a.y * b.x));
|
|
||||||
}
|
|
||||||
static inline float v3_len(vec3 a) { return sqrtf(v3_dot(a, a)); }
|
|
||||||
static inline vec3 v3_norm(vec3 a)
|
|
||||||
{
|
|
||||||
float l = v3_len(a);
|
|
||||||
return l > 1e-6F ? v3_scale(a, 1.0F / l) : v3(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- Global state --------
|
|
||||||
static int g_win_w = 1280, g_win_h = 720;
|
|
||||||
static bool g_keys[256] = {0};
|
|
||||||
static bool g_captured_mouse = true;
|
|
||||||
static int g_last_mouse_x = -1, g_last_mouse_y = -1;
|
|
||||||
static bool g_ignore_next_passive = false; // avoid warp feedback loops
|
|
||||||
|
|
||||||
static vec3 g_cam_pos = {0.0F, 1.6F, 5.0F};
|
|
||||||
static float g_yaw_deg = -90.0F; // facing -Z initially
|
|
||||||
static float g_pitch_deg = 0.0F;
|
|
||||||
|
|
||||||
static float g_move_speed = 4.0F; // m/s
|
|
||||||
static float g_sprint_mul = 1.8F;
|
|
||||||
static float g_mouse_sens = 0.12F; // deg per pixel
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
vec3 pos;
|
|
||||||
float radius;
|
|
||||||
float speed;
|
|
||||||
} Target;
|
|
||||||
static const int MAX_TARGETS = 128;
|
|
||||||
static Target g_targets[128];
|
|
||||||
static int g_target_count = 0;
|
|
||||||
static float g_spawn_timer = 0.0F;
|
|
||||||
static float g_spawn_interval = 1.2F; // seconds
|
|
||||||
static int g_score = 0;
|
|
||||||
|
|
||||||
typedef enum
|
|
||||||
{
|
|
||||||
GAME_RUNNING = 0,
|
|
||||||
GAME_OVER = 1
|
|
||||||
} GameState;
|
|
||||||
static GameState g_state = GAME_RUNNING;
|
|
||||||
|
|
||||||
// Bullet visualization removed per request
|
|
||||||
|
|
||||||
// Timing
|
|
||||||
static int g_prev_ms = 0;
|
|
||||||
|
|
||||||
// -------- Utility --------
|
|
||||||
static float clampf(float v, float lo, float hi) { return v < lo ? lo : (v > hi ? hi : v); }
|
|
||||||
static float deg2rad(float d) { return d * (float)M_PI / 180.0F; }
|
|
||||||
|
|
||||||
static vec3 cam_front(void)
|
|
||||||
{
|
|
||||||
float yaw = deg2rad(g_yaw_deg);
|
|
||||||
float pitch = deg2rad(g_pitch_deg);
|
|
||||||
vec3 f = {cosf(pitch) * cosf(yaw), sinf(pitch), cosf(pitch) * sinf(yaw)};
|
|
||||||
return v3_norm(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
static vec3 cam_right(void) { return v3_norm(v3_cross(cam_front(), v3(0, 1, 0))); }
|
|
||||||
|
|
||||||
// -------- Audio (SDL2) --------
|
|
||||||
static SDL_AudioDeviceID g_audio_dev = 0;
|
|
||||||
static SDL_AudioSpec g_audio_have;
|
|
||||||
static bool g_audio_ok = false;
|
|
||||||
|
|
||||||
static void audio_cleanup(void)
|
|
||||||
{
|
|
||||||
if (g_audio_dev)
|
|
||||||
{
|
|
||||||
SDL_ClearQueuedAudio(g_audio_dev);
|
|
||||||
SDL_CloseAudioDevice(g_audio_dev);
|
|
||||||
g_audio_dev = 0;
|
|
||||||
}
|
|
||||||
if (g_audio_ok)
|
|
||||||
{
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
||||||
g_audio_ok = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void audio_init(void)
|
|
||||||
{
|
|
||||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "SDL audio init failed: %s\n", SDL_GetError());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SDL_AudioSpec want;
|
|
||||||
SDL_zero(want);
|
|
||||||
want.freq = 48000;
|
|
||||||
want.format = AUDIO_F32SYS;
|
|
||||||
want.channels = 1;
|
|
||||||
want.samples = 1024;
|
|
||||||
g_audio_dev = SDL_OpenAudioDevice(NULL, 0, &want, &g_audio_have, 0);
|
|
||||||
if (!g_audio_dev)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "SDL_OpenAudioDevice failed: %s\n", SDL_GetError());
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SDL_PauseAudioDevice(g_audio_dev, 0);
|
|
||||||
g_audio_ok = true;
|
|
||||||
atexit(audio_cleanup);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void audio_queue_samples(const float *data, int frames)
|
|
||||||
{
|
|
||||||
if (!g_audio_ok)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int bytes = frames * (int)sizeof(float);
|
|
||||||
SDL_QueueAudio(g_audio_dev, data, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void audio_play_tone(float freq, float duration, float vol)
|
|
||||||
{
|
|
||||||
if (!g_audio_ok)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int sr = g_audio_have.freq ? g_audio_have.freq : 48000;
|
|
||||||
int frames = (int)(duration * sr);
|
|
||||||
if (frames <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
float *buf = (float *)malloc((size_t)frames * sizeof(float));
|
|
||||||
if (!buf)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
float phase = 0.0F;
|
|
||||||
float dp = 2.0F * (float)M_PI * freq / (float)sr;
|
|
||||||
for (int i = 0; i < frames; ++i)
|
|
||||||
{
|
|
||||||
float t = (float)i / (float)frames;
|
|
||||||
// simple attack/decay envelope
|
|
||||||
float env = t < 0.1F ? (t / 0.1F) : (1.0F - t);
|
|
||||||
if (env < 0.0F)
|
|
||||||
{
|
|
||||||
env = 0.0F;
|
|
||||||
}
|
|
||||||
buf[i] = sinf(phase) * vol * env;
|
|
||||||
phase += dp;
|
|
||||||
}
|
|
||||||
audio_queue_samples(buf, frames);
|
|
||||||
free(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void audio_play_sweep(float f0, float f1, float duration, float vol)
|
|
||||||
{
|
|
||||||
if (!g_audio_ok)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int sr = g_audio_have.freq ? g_audio_have.freq : 48000;
|
|
||||||
int frames = (int)(duration * sr);
|
|
||||||
if (frames <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
float *buf = (float *)malloc((size_t)frames * sizeof(float));
|
|
||||||
if (!buf)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
float phase = 0.0F;
|
|
||||||
for (int i = 0; i < frames; ++i)
|
|
||||||
{
|
|
||||||
float t = (float)i / (float)frames; // 0..1
|
|
||||||
float f = f0 + ((f1 - f0) * t);
|
|
||||||
float dp = 2.0F * (float)M_PI * f / (float)sr;
|
|
||||||
float env = 1.0F - t; // fade out
|
|
||||||
buf[i] = sinf(phase) * vol * env;
|
|
||||||
phase += dp;
|
|
||||||
}
|
|
||||||
audio_queue_samples(buf, frames);
|
|
||||||
free(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static float frand(float a, float b) { return a + ((b - a) * (rand() / (float)RAND_MAX)); }
|
|
||||||
|
|
||||||
static void clear_targets(void) { g_target_count = 0; }
|
|
||||||
|
|
||||||
static void spawn_target(void)
|
|
||||||
{
|
|
||||||
if (g_target_count >= MAX_TARGETS)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
float radius_spawn = frand(22.0F, 34.0F);
|
|
||||||
float ang = frand(0.0F, 2.0F * (float)M_PI);
|
|
||||||
vec3 p = v3(cosf(ang) * radius_spawn, 0.5F, sinf(ang) * radius_spawn);
|
|
||||||
Target t;
|
|
||||||
t.pos = p;
|
|
||||||
t.radius = 0.6F;
|
|
||||||
t.speed = frand(1.4F, 3.2F);
|
|
||||||
g_targets[g_target_count++] = t;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void reset_game(void)
|
|
||||||
{
|
|
||||||
g_score = 0;
|
|
||||||
g_spawn_timer = 0.0F;
|
|
||||||
g_spawn_interval = 1.2F;
|
|
||||||
clear_targets();
|
|
||||||
// spawn a few to start
|
|
||||||
for (int i = 0; i < 4; ++i)
|
|
||||||
{
|
|
||||||
spawn_target();
|
|
||||||
}
|
|
||||||
g_state = GAME_RUNNING;
|
|
||||||
if (g_captured_mouse)
|
|
||||||
{
|
|
||||||
g_ignore_next_passive = true;
|
|
||||||
glutWarpPointer(g_win_w / 2, g_win_h / 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ray-sphere intersection: returns t >= 0 for first hit, or -1 if miss
|
|
||||||
static float ray_sphere(vec3 ro, vec3 rd, vec3 c, float r)
|
|
||||||
{
|
|
||||||
vec3 oc = v3_sub(ro, c);
|
|
||||||
float b = v3_dot(oc, rd);
|
|
||||||
float cterm = v3_dot(oc, oc) - (r * r);
|
|
||||||
float disc = (b * b) - cterm;
|
|
||||||
if (disc < 0.0F)
|
|
||||||
{
|
|
||||||
return -1.0F;
|
|
||||||
}
|
|
||||||
float t = -b - sqrtf(disc);
|
|
||||||
return t >= 0.0F ? t : -1.0F;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void shoot(void)
|
|
||||||
{
|
|
||||||
if (g_state != GAME_RUNNING)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
vec3 dir = cam_front();
|
|
||||||
float best_t = 1e9F;
|
|
||||||
int best_i = -1;
|
|
||||||
for (int i = 0; i < g_target_count; ++i)
|
|
||||||
{
|
|
||||||
float t = ray_sphere(g_cam_pos, dir, g_targets[i].pos, g_targets[i].radius);
|
|
||||||
if (t >= 0.0F && t < best_t)
|
|
||||||
{
|
|
||||||
best_t = t;
|
|
||||||
best_i = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
audio_play_tone(1600.0F, 0.05F, 0.25F); // shoot
|
|
||||||
|
|
||||||
if (best_i >= 0)
|
|
||||||
{
|
|
||||||
// remove hit target (swap remove)
|
|
||||||
g_targets[best_i] = g_targets[g_target_count - 1];
|
|
||||||
g_target_count--;
|
|
||||||
g_score++;
|
|
||||||
audio_play_tone(600.0F, 0.08F, 0.35F); // hit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- Rendering --------
|
|
||||||
static void draw_grid(float half, float step)
|
|
||||||
{
|
|
||||||
glColor3f(0.2F, 0.25F, 0.3F);
|
|
||||||
glBegin(GL_LINES);
|
|
||||||
for (float x = -half; x <= half + 1e-4F; x += step)
|
|
||||||
{
|
|
||||||
glVertex3f(x, 0.0F, -half);
|
|
||||||
glVertex3f(x, 0.0F, half);
|
|
||||||
}
|
|
||||||
for (float z = -half; z <= half + 1e-4F; z += step)
|
|
||||||
{
|
|
||||||
glVertex3f(-half, 0.0F, z);
|
|
||||||
glVertex3f(half, 0.0F, z);
|
|
||||||
}
|
|
||||||
glEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void draw_cube(void)
|
|
||||||
{
|
|
||||||
// Simple colored cube (size 1)
|
|
||||||
const float s = 0.5F;
|
|
||||||
glBegin(GL_QUADS);
|
|
||||||
// +X
|
|
||||||
glColor3f(1, 0, 0);
|
|
||||||
glVertex3f(s, -s, -s);
|
|
||||||
glVertex3f(s, -s, s);
|
|
||||||
glVertex3f(s, s, s);
|
|
||||||
glVertex3f(s, s, -s);
|
|
||||||
// -X
|
|
||||||
glColor3f(0.8F, 0, 0);
|
|
||||||
glVertex3f(-s, -s, -s);
|
|
||||||
glVertex3f(-s, s, -s);
|
|
||||||
glVertex3f(-s, s, s);
|
|
||||||
glVertex3f(-s, -s, s);
|
|
||||||
// +Y
|
|
||||||
glColor3f(0.9F, 0.1F, 0.1F);
|
|
||||||
glVertex3f(-s, s, -s);
|
|
||||||
glVertex3f(s, s, -s);
|
|
||||||
glVertex3f(s, s, s);
|
|
||||||
glVertex3f(-s, s, s);
|
|
||||||
// -Y
|
|
||||||
glColor3f(0.6F, 0.05F, 0.05F);
|
|
||||||
glVertex3f(-s, -s, -s);
|
|
||||||
glVertex3f(-s, -s, s);
|
|
||||||
glVertex3f(s, -s, s);
|
|
||||||
glVertex3f(s, -s, -s);
|
|
||||||
// +Z
|
|
||||||
glColor3f(1, 0.2F, 0.2F);
|
|
||||||
glVertex3f(-s, -s, s);
|
|
||||||
glVertex3f(-s, s, s);
|
|
||||||
glVertex3f(s, s, s);
|
|
||||||
glVertex3f(s, -s, s);
|
|
||||||
// -Z
|
|
||||||
glColor3f(0.7F, 0.1F, 0.1F);
|
|
||||||
glVertex3f(-s, -s, -s);
|
|
||||||
glVertex3f(s, -s, -s);
|
|
||||||
glVertex3f(s, s, -s);
|
|
||||||
glVertex3f(-s, s, -s);
|
|
||||||
glEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void draw_crosshair(void)
|
|
||||||
{
|
|
||||||
glMatrixMode(GL_PROJECTION);
|
|
||||||
glPushMatrix();
|
|
||||||
glLoadIdentity();
|
|
||||||
gluOrtho2D(0, g_win_w, g_win_h, 0);
|
|
||||||
glMatrixMode(GL_MODELVIEW);
|
|
||||||
glPushMatrix();
|
|
||||||
glLoadIdentity();
|
|
||||||
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
|
||||||
glColor3f(0.95F, 0.95F, 0.95F);
|
|
||||||
int cx = g_win_w / 2;
|
|
||||||
int cy = g_win_h / 2;
|
|
||||||
int s = 8;
|
|
||||||
glBegin(GL_LINES);
|
|
||||||
glVertex2i(cx - s, cy);
|
|
||||||
glVertex2i(cx + s, cy);
|
|
||||||
glVertex2i(cx, cy - s);
|
|
||||||
glVertex2i(cx, cy + s);
|
|
||||||
glEnd();
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
|
||||||
|
|
||||||
glMatrixMode(GL_MODELVIEW);
|
|
||||||
glPopMatrix();
|
|
||||||
glMatrixMode(GL_PROJECTION);
|
|
||||||
glPopMatrix();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void display(void)
|
|
||||||
{
|
|
||||||
glClearColor(0.05F, 0.06F, 0.08F, 1.0F);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
||||||
|
|
||||||
glMatrixMode(GL_MODELVIEW);
|
|
||||||
glLoadIdentity();
|
|
||||||
|
|
||||||
vec3 front = cam_front();
|
|
||||||
vec3 at = v3_add(g_cam_pos, front);
|
|
||||||
gluLookAt(g_cam_pos.x, g_cam_pos.y, g_cam_pos.z, at.x, at.y, at.z, 0, 1, 0);
|
|
||||||
|
|
||||||
// Ground grid
|
|
||||||
draw_grid(40.0F, 1.0F);
|
|
||||||
|
|
||||||
// Target cubes
|
|
||||||
for (int i = 0; i < g_target_count; ++i)
|
|
||||||
{
|
|
||||||
glPushMatrix();
|
|
||||||
glTranslatef(g_targets[i].pos.x, g_targets[i].pos.y, g_targets[i].pos.z);
|
|
||||||
draw_cube();
|
|
||||||
glPopMatrix();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bullet line removed
|
|
||||||
|
|
||||||
// Crosshair overlay (only during gameplay)
|
|
||||||
if (g_state == GAME_RUNNING)
|
|
||||||
{
|
|
||||||
draw_crosshair();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Game over overlay text
|
|
||||||
if (g_state == GAME_OVER)
|
|
||||||
{
|
|
||||||
// Switch to 2D for text
|
|
||||||
glMatrixMode(GL_PROJECTION);
|
|
||||||
glPushMatrix();
|
|
||||||
glLoadIdentity();
|
|
||||||
gluOrtho2D(0, g_win_w, g_win_h, 0);
|
|
||||||
glMatrixMode(GL_MODELVIEW);
|
|
||||||
glPushMatrix();
|
|
||||||
glLoadIdentity();
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
|
||||||
|
|
||||||
const char *line1 = "GAME OVER";
|
|
||||||
char line2[64];
|
|
||||||
snprintf(line2, sizeof(line2), "Score: %d", g_score);
|
|
||||||
const char *line3 = "Press R to restart or Esc to quit";
|
|
||||||
|
|
||||||
void *font = GLUT_BITMAP_HELVETICA_18;
|
|
||||||
int cx = g_win_w / 2;
|
|
||||||
int cy = g_win_h / 2;
|
|
||||||
glColor3f(1, 1, 1);
|
|
||||||
// naive center: estimate width by char count * 9 px
|
|
||||||
int w1 = (int)(9 * strlen(line1));
|
|
||||||
int w2 = (int)(9 * strlen(line2));
|
|
||||||
int w3 = (int)(9 * strlen(line3));
|
|
||||||
|
|
||||||
glRasterPos2i(cx - (w1 / 2), cy - 30);
|
|
||||||
for (const char *p = line1; *p; ++p)
|
|
||||||
{
|
|
||||||
glutBitmapCharacter(font, *p);
|
|
||||||
}
|
|
||||||
glRasterPos2i(cx - (w2 / 2), cy - 8);
|
|
||||||
for (const char *p = line2; *p; ++p)
|
|
||||||
{
|
|
||||||
glutBitmapCharacter(font, *p);
|
|
||||||
}
|
|
||||||
glRasterPos2i(cx - (w3 / 2), cy + 18);
|
|
||||||
for (const char *p = line3; *p; ++p)
|
|
||||||
{
|
|
||||||
glutBitmapCharacter(font, *p);
|
|
||||||
}
|
|
||||||
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
|
||||||
glMatrixMode(GL_MODELVIEW);
|
|
||||||
glPopMatrix();
|
|
||||||
glMatrixMode(GL_PROJECTION);
|
|
||||||
glPopMatrix();
|
|
||||||
}
|
|
||||||
|
|
||||||
glutSwapBuffers();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- Update/Input --------
|
|
||||||
static void update(float dt)
|
|
||||||
{
|
|
||||||
if (g_state == GAME_RUNNING)
|
|
||||||
{
|
|
||||||
float speed =
|
|
||||||
g_move_speed * ((g_keys['\t'] || g_keys['q']) ? g_sprint_mul : 1.0F); // Tab/q to sprint
|
|
||||||
vec3 f = cam_front();
|
|
||||||
f.y = 0.0F;
|
|
||||||
f = v3_norm(f);
|
|
||||||
vec3 r = cam_right();
|
|
||||||
r.y = 0.0F;
|
|
||||||
r = v3_norm(r);
|
|
||||||
// Accumulate movement and apply once to avoid drift artifacts
|
|
||||||
vec3 mv = v3(0, 0, 0);
|
|
||||||
if (g_keys['w'])
|
|
||||||
{
|
|
||||||
mv = v3_add(mv, f);
|
|
||||||
}
|
|
||||||
if (g_keys['s'])
|
|
||||||
{
|
|
||||||
mv = v3_sub(mv, f);
|
|
||||||
}
|
|
||||||
if (g_keys['a'])
|
|
||||||
{
|
|
||||||
mv = v3_sub(mv, r);
|
|
||||||
}
|
|
||||||
if (g_keys['d'])
|
|
||||||
{
|
|
||||||
mv = v3_add(mv, r);
|
|
||||||
}
|
|
||||||
if (v3_len(mv) > 0.0F)
|
|
||||||
{
|
|
||||||
mv = v3_norm(mv);
|
|
||||||
g_cam_pos = v3_add(g_cam_pos, v3_scale(mv, speed * dt));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep feet on ground
|
|
||||||
g_cam_pos.y = 1.6F;
|
|
||||||
|
|
||||||
// Move targets toward player and check collision
|
|
||||||
for (int i = 0; i < g_target_count; ++i)
|
|
||||||
{
|
|
||||||
vec3 to_player = v3_sub(g_cam_pos, g_targets[i].pos);
|
|
||||||
to_player.y = 0.0F;
|
|
||||||
vec3 dir = v3_norm(to_player);
|
|
||||||
g_targets[i].pos = v3_add(g_targets[i].pos, v3_scale(dir, g_targets[i].speed * dt));
|
|
||||||
g_targets[i].pos.y = 0.5F;
|
|
||||||
|
|
||||||
float dist2 = (to_player.x * to_player.x) + (to_player.z * to_player.z);
|
|
||||||
float reach = (0.6F + g_targets[i].radius);
|
|
||||||
if (dist2 <= reach * reach)
|
|
||||||
{
|
|
||||||
g_state = GAME_OVER;
|
|
||||||
glutSetCursor(GLUT_CURSOR_LEFT_ARROW);
|
|
||||||
audio_play_sweep(400.0F, 120.0F, 0.5F, 0.5F); // game over
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spawn new targets over time (slightly accelerate spawn rate)
|
|
||||||
g_spawn_timer += dt;
|
|
||||||
if (g_spawn_timer >= g_spawn_interval)
|
|
||||||
{
|
|
||||||
g_spawn_timer = 0.0F;
|
|
||||||
spawn_target();
|
|
||||||
g_spawn_interval = fmaxf(0.4F, g_spawn_interval * 0.98F);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No bullet tracer timer
|
|
||||||
}
|
|
||||||
|
|
||||||
static void idle(void)
|
|
||||||
{
|
|
||||||
int now = glutGet(GLUT_ELAPSED_TIME);
|
|
||||||
if (g_prev_ms == 0)
|
|
||||||
{
|
|
||||||
g_prev_ms = now;
|
|
||||||
}
|
|
||||||
float dt = (now - g_prev_ms) / 1000.0F;
|
|
||||||
g_prev_ms = now;
|
|
||||||
|
|
||||||
update(dt);
|
|
||||||
glutPostRedisplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void reshape(int w, int h)
|
|
||||||
{
|
|
||||||
g_win_w = w > 1 ? w : 1;
|
|
||||||
g_win_h = h > 1 ? h : 1;
|
|
||||||
glViewport(0, 0, g_win_w, g_win_h);
|
|
||||||
glMatrixMode(GL_PROJECTION);
|
|
||||||
glLoadIdentity();
|
|
||||||
gluPerspective(75.0, (double)g_win_w / (double)g_win_h, 0.05, 500.0);
|
|
||||||
glMatrixMode(GL_MODELVIEW);
|
|
||||||
|
|
||||||
if (g_captured_mouse && g_state == GAME_RUNNING)
|
|
||||||
{
|
|
||||||
g_ignore_next_passive = true;
|
|
||||||
glutWarpPointer(g_win_w / 2, g_win_h / 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void keyboard_down(unsigned char key, int x, int y)
|
|
||||||
{
|
|
||||||
(void)x;
|
|
||||||
(void)y;
|
|
||||||
unsigned char k = (unsigned char)tolower(key);
|
|
||||||
g_keys[k] = true;
|
|
||||||
if (key == 27)
|
|
||||||
{ // Esc
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
else if (key == ' ')
|
|
||||||
{
|
|
||||||
shoot();
|
|
||||||
}
|
|
||||||
else if (key == 'm' || key == 'M')
|
|
||||||
{
|
|
||||||
g_captured_mouse = !g_captured_mouse;
|
|
||||||
glutSetCursor(g_captured_mouse ? GLUT_CURSOR_NONE : GLUT_CURSOR_LEFT_ARROW);
|
|
||||||
if (g_captured_mouse && g_state == GAME_RUNNING)
|
|
||||||
{
|
|
||||||
g_ignore_next_passive = true;
|
|
||||||
glutWarpPointer(g_win_w / 2, g_win_h / 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (key == 'r' || key == 'R')
|
|
||||||
{
|
|
||||||
if (g_state == GAME_OVER)
|
|
||||||
{
|
|
||||||
glutSetCursor(g_captured_mouse ? GLUT_CURSOR_NONE : GLUT_CURSOR_LEFT_ARROW);
|
|
||||||
reset_game();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void keyboard_up(unsigned char key, int x, int y)
|
|
||||||
{
|
|
||||||
(void)x;
|
|
||||||
(void)y;
|
|
||||||
unsigned char k = (unsigned char)tolower(key);
|
|
||||||
g_keys[k] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void mouse_button(int button, int state, int x, int y)
|
|
||||||
{
|
|
||||||
(void)x;
|
|
||||||
(void)y;
|
|
||||||
if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
|
|
||||||
{
|
|
||||||
shoot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void passive_motion(int x, int y)
|
|
||||||
{
|
|
||||||
if (!g_captured_mouse || g_state != GAME_RUNNING)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (g_ignore_next_passive)
|
|
||||||
{ // ignore event caused by warp
|
|
||||||
g_ignore_next_passive = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (g_last_mouse_x < 0)
|
|
||||||
{
|
|
||||||
g_last_mouse_x = x;
|
|
||||||
g_last_mouse_y = y;
|
|
||||||
}
|
|
||||||
int dx = x - (g_win_w / 2);
|
|
||||||
int dy = y - (g_win_h / 2);
|
|
||||||
|
|
||||||
g_yaw_deg += dx * g_mouse_sens;
|
|
||||||
g_pitch_deg -= dy * g_mouse_sens;
|
|
||||||
g_pitch_deg = clampf(g_pitch_deg, -89.0F, 89.0F);
|
|
||||||
|
|
||||||
g_ignore_next_passive = true;
|
|
||||||
glutWarpPointer(g_win_w / 2, g_win_h / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- Init --------
|
|
||||||
static void init_gl(void)
|
|
||||||
{
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
|
||||||
glEnable(GL_CULL_FACE);
|
|
||||||
glCullFace(GL_BACK);
|
|
||||||
glLineWidth(1.0F);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void on_entry(int state)
|
|
||||||
{
|
|
||||||
if (state != GLUT_ENTERED)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 256; ++i)
|
|
||||||
{
|
|
||||||
g_keys[i] = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
(void)argv;
|
|
||||||
srand((unsigned)time(NULL));
|
|
||||||
|
|
||||||
glutInit(&argc, argv);
|
|
||||||
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
|
|
||||||
glutInitWindowSize(g_win_w, g_win_h);
|
|
||||||
glutCreateWindow("FPS Demo");
|
|
||||||
|
|
||||||
init_gl();
|
|
||||||
audio_init();
|
|
||||||
|
|
||||||
glutDisplayFunc(display);
|
|
||||||
glutIdleFunc(idle);
|
|
||||||
glutReshapeFunc(reshape);
|
|
||||||
glutKeyboardFunc(keyboard_down);
|
|
||||||
glutKeyboardUpFunc(keyboard_up);
|
|
||||||
// Clear keys on focus leave to avoid stuck inputs
|
|
||||||
glutEntryFunc(on_entry);
|
|
||||||
glutPassiveMotionFunc(passive_motion);
|
|
||||||
glutMouseFunc(mouse_button);
|
|
||||||
|
|
||||||
// Start with mouse captured
|
|
||||||
glutSetCursor(GLUT_CURSOR_NONE);
|
|
||||||
g_ignore_next_passive = true;
|
|
||||||
glutWarpPointer(g_win_w / 2, g_win_h / 2);
|
|
||||||
|
|
||||||
reset_game();
|
|
||||||
|
|
||||||
glutMainLoop();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
12
C/fps/run.sh
12
C/fps/run.sh
@ -1,12 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
# Install dependencies
|
|
||||||
if command -v apt-get &>/dev/null; then
|
|
||||||
sudo apt-get install -y freeglut3-dev libglu1-mesa-dev libsdl2-dev
|
|
||||||
elif command -v pacman &>/dev/null; then
|
|
||||||
pacman -Q freeglut sdl2 &>/dev/null || sudo pacman -S --noconfirm freeglut sdl2
|
|
||||||
elif command -v dnf &>/dev/null; then
|
|
||||||
sudo dnf install -y freeglut-devel mesa-libGLU-devel SDL2-devel
|
|
||||||
fi
|
|
||||||
make
|
|
||||||
./fps_demo
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
# Clang-format configuration for imageViewer project
|
|
||||||
---
|
|
||||||
Language: Cpp
|
|
||||||
# Base style
|
|
||||||
BasedOnStyle: LLVM
|
|
||||||
|
|
||||||
# Indentation
|
|
||||||
IndentWidth: 4
|
|
||||||
TabWidth: 4
|
|
||||||
UseTab: Never
|
|
||||||
IndentCaseLabels: true
|
|
||||||
|
|
||||||
# Braces
|
|
||||||
BreakBeforeBraces: Attach
|
|
||||||
Cpp11BracedListStyle: true
|
|
||||||
|
|
||||||
# Line length and breaking
|
|
||||||
ColumnLimit: 100
|
|
||||||
BreakBeforeBinaryOperators: None
|
|
||||||
BreakBeforeTernaryOperators: true
|
|
||||||
AlwaysBreakAfterReturnType: None
|
|
||||||
|
|
||||||
# Spacing
|
|
||||||
SpaceBeforeParens: ControlStatements
|
|
||||||
SpaceInEmptyParentheses: false
|
|
||||||
SpacesInParentheses: false
|
|
||||||
SpacesInSquareBrackets: false
|
|
||||||
SpaceAfterCStyleCast: false
|
|
||||||
SpaceBeforeAssignmentOperators: true
|
|
||||||
|
|
||||||
# Alignment
|
|
||||||
AlignOperands: true
|
|
||||||
AlignConsecutiveAssignments: false
|
|
||||||
AlignConsecutiveDeclarations: false
|
|
||||||
AlignTrailingComments: true
|
|
||||||
|
|
||||||
# Function parameters
|
|
||||||
AllowAllParametersOfDeclarationOnNextLine: false
|
|
||||||
BinPackParameters: false
|
|
||||||
BinPackArguments: false
|
|
||||||
|
|
||||||
# Includes
|
|
||||||
SortIncludes: true
|
|
||||||
IncludeBlocks: Preserve
|
|
||||||
|
|
||||||
# Comments
|
|
||||||
ReflowComments: true
|
|
||||||
|
|
||||||
# Pointers and references
|
|
||||||
PointerAlignment: Right
|
|
||||||
DerivePointerAlignment: false
|
|
||||||
|
|
||||||
# Control statements
|
|
||||||
AllowShortIfStatementsOnASingleLine: false
|
|
||||||
AllowShortLoopsOnASingleLine: false
|
|
||||||
AllowShortFunctionsOnASingleLine: None
|
|
||||||
AllowShortCaseLabelsOnASingleLine: false
|
|
||||||
|
|
||||||
# Misc
|
|
||||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
|
||||||
MaxEmptyLinesToKeep: 1
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
# Clang-tidy configuration for imageViewer project
|
|
||||||
Checks: >
|
|
||||||
clang-diagnostic-*,
|
|
||||||
clang-analyzer-*,
|
|
||||||
bugprone-*,
|
|
||||||
cert-*,
|
|
||||||
misc-*,
|
|
||||||
modernize-*,
|
|
||||||
performance-*,
|
|
||||||
portability-*,
|
|
||||||
readability-*,
|
|
||||||
-readability-magic-numbers,
|
|
||||||
-modernize-use-trailing-return-type,
|
|
||||||
-cert-err33-c,
|
|
||||||
-misc-unused-parameters,
|
|
||||||
-readability-isolate-declaration,
|
|
||||||
-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling
|
|
||||||
|
|
||||||
WarningsAsErrors: ""
|
|
||||||
HeaderFilterRegex: '.*\.h$'
|
|
||||||
AnalyzeTemporaryDtors: false
|
|
||||||
FormatStyle: file
|
|
||||||
CheckOptions:
|
|
||||||
- key: readability-identifier-naming.VariableCase
|
|
||||||
value: snake_case
|
|
||||||
- key: readability-identifier-naming.FunctionCase
|
|
||||||
value: snake_case
|
|
||||||
- key: readability-identifier-naming.MacroCase
|
|
||||||
value: UPPER_CASE
|
|
||||||
- key: readability-identifier-naming.TypedefCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-identifier-naming.StructCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-function-size.LineThreshold
|
|
||||||
value: 100
|
|
||||||
- key: readability-function-size.StatementThreshold
|
|
||||||
value: 50
|
|
||||||
- key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
|
|
||||||
value: true
|
|
||||||
35
C/imageViewer/.vscode/settings.json
vendored
35
C/imageViewer/.vscode/settings.json
vendored
@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"C_Cpp.default.cStandard": "c99",
|
|
||||||
"C_Cpp.default.intelliSenseMode": "linux-gcc-x64",
|
|
||||||
"C_Cpp.default.includePath": ["${workspaceFolder}/**", "/usr/include/SDL2"],
|
|
||||||
"C_Cpp.default.defines": ["_REENTRANT"],
|
|
||||||
"C_Cpp.default.compilerPath": "/usr/bin/gcc",
|
|
||||||
"C_Cpp.clang_format_style": "file",
|
|
||||||
"C_Cpp.clang_format_fallbackStyle": "LLVM",
|
|
||||||
|
|
||||||
"files.associations": {
|
|
||||||
"*.h": "c",
|
|
||||||
"*.c": "c"
|
|
||||||
},
|
|
||||||
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.tabSize": 4,
|
|
||||||
"editor.insertSpaces": true,
|
|
||||||
"editor.rulers": [100],
|
|
||||||
|
|
||||||
"clang-tidy.executable": "clang-tidy",
|
|
||||||
"clang-tidy.checks": [
|
|
||||||
"clang-diagnostic-*",
|
|
||||||
"clang-analyzer-*",
|
|
||||||
"bugprone-*",
|
|
||||||
"cert-*",
|
|
||||||
"misc-*",
|
|
||||||
"performance-*",
|
|
||||||
"portability-*",
|
|
||||||
"readability-*"
|
|
||||||
],
|
|
||||||
|
|
||||||
"cppcheck.enable": true,
|
|
||||||
"cppcheck.standard": ["c99"],
|
|
||||||
"cppcheck.suppress": ["missingIncludeSystem", "unusedFunction"]
|
|
||||||
}
|
|
||||||
@ -1,137 +0,0 @@
|
|||||||
# C Language Linter Setup for imageViewer Project
|
|
||||||
|
|
||||||
This directory contains a comprehensive linting setup for the imageViewer C project.
|
|
||||||
|
|
||||||
## Tools Included
|
|
||||||
|
|
||||||
### 1. **clang-tidy** - Static Analysis
|
|
||||||
|
|
||||||
- Configuration: `.clang-tidy`
|
|
||||||
- Checks for bugs, performance issues, and style violations
|
|
||||||
- Enforces modern C coding standards
|
|
||||||
|
|
||||||
### 2. **clang-format** - Code Formatting
|
|
||||||
|
|
||||||
- Configuration: `.clang-format`
|
|
||||||
- Automatically formats code to consistent style
|
|
||||||
- 100-character line limit, 4-space indentation
|
|
||||||
|
|
||||||
### 3. **cppcheck** - Additional Static Analysis
|
|
||||||
|
|
||||||
- Detects memory leaks, null pointer dereferences
|
|
||||||
- Checks for undefined behavior
|
|
||||||
|
|
||||||
### 4. **gcc with warnings** - Compiler Analysis
|
|
||||||
|
|
||||||
- Comprehensive warning flags
|
|
||||||
- Standards compliance checking
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install dependencies (Arch Linux)
|
|
||||||
make deps-arch
|
|
||||||
|
|
||||||
# Run all lint checks
|
|
||||||
make lint
|
|
||||||
|
|
||||||
# Format code
|
|
||||||
make format
|
|
||||||
|
|
||||||
# Run all checks
|
|
||||||
make check
|
|
||||||
|
|
||||||
# Check for memory leaks
|
|
||||||
make memcheck
|
|
||||||
```
|
|
||||||
|
|
||||||
### Individual Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Manual linting
|
|
||||||
./lint.sh
|
|
||||||
|
|
||||||
# Format specific file
|
|
||||||
clang-format -i main.c
|
|
||||||
|
|
||||||
# Run clang-tidy
|
|
||||||
clang-tidy main.c -- -I/usr/include/SDL2 -D_REENTRANT
|
|
||||||
|
|
||||||
# Run cppcheck
|
|
||||||
cppcheck --enable=all main.c
|
|
||||||
```
|
|
||||||
|
|
||||||
## VS Code Integration
|
|
||||||
|
|
||||||
The `.vscode/settings.json` file provides:
|
|
||||||
|
|
||||||
- Automatic formatting on save
|
|
||||||
- C99 standard compliance
|
|
||||||
- IntelliSense configuration for SDL2
|
|
||||||
- Integrated linting with clang-tidy and cppcheck
|
|
||||||
|
|
||||||
## Recommended Extensions for VS Code
|
|
||||||
|
|
||||||
- C/C++ (Microsoft)
|
|
||||||
- clang-tidy (mine-cetinkaya-fianso)
|
|
||||||
- cppcheck (unixwrapped)
|
|
||||||
|
|
||||||
## Linting Rules
|
|
||||||
|
|
||||||
### Enabled Checks
|
|
||||||
|
|
||||||
- **clang-diagnostic-\***: Compiler diagnostics
|
|
||||||
- **clang-analyzer-\***: Static analysis
|
|
||||||
- **bugprone-\***: Bug-prone patterns
|
|
||||||
- **cert-\***: CERT secure coding standards
|
|
||||||
- **misc-\***: Miscellaneous checks
|
|
||||||
- **performance-\***: Performance improvements
|
|
||||||
- **portability-\***: Cross-platform issues
|
|
||||||
- **readability-\***: Code readability
|
|
||||||
|
|
||||||
### Disabled Checks
|
|
||||||
|
|
||||||
- `readability-magic-numbers`: Allows constants like window dimensions
|
|
||||||
- `cert-err33-c`: Allows ignoring some function return values
|
|
||||||
- `misc-unused-parameters`: Common in callback functions
|
|
||||||
|
|
||||||
## Code Quality Workflow
|
|
||||||
|
|
||||||
1. **Write Code**: Develop features in `main.c`
|
|
||||||
2. **Lint**: Run `make lint` to check for issues
|
|
||||||
3. **Format**: Run `make format` to fix formatting
|
|
||||||
4. **Build**: Run `make` to compile
|
|
||||||
5. **Test**: Run `make test` with sample images
|
|
||||||
6. **Memory Check**: Run `make memcheck` for leak detection
|
|
||||||
|
|
||||||
## Configuration Files
|
|
||||||
|
|
||||||
- `.clang-tidy`: Static analysis rules
|
|
||||||
- `.clang-format`: Code formatting style
|
|
||||||
- `.vscode/settings.json`: VS Code integration
|
|
||||||
- `lint.sh`: Comprehensive linting script
|
|
||||||
- `Makefile`: Build and quality targets
|
|
||||||
|
|
||||||
## Installation on Different Systems
|
|
||||||
|
|
||||||
### Arch Linux
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo pacman -S clang cppcheck valgrind
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ubuntu/Debian
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install clang-tidy cppcheck clang-format valgrind
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fedora/RHEL
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo dnf install clang-tools-extra cppcheck clang valgrind
|
|
||||||
```
|
|
||||||
|
|
||||||
This setup ensures high code quality, consistency, and helps catch potential issues early in development.
|
|
||||||
@ -1,101 +0,0 @@
|
|||||||
CC = gcc
|
|
||||||
CFLAGS = -Wall -Wextra -std=c23 -O2
|
|
||||||
LIBS = -lSDL2 -lSDL2_image -lm
|
|
||||||
TARGET = imageviewer
|
|
||||||
SOURCE = main.c
|
|
||||||
|
|
||||||
# Check if pkg-config is available for SDL2
|
|
||||||
SDL2_CFLAGS := $(shell pkg-config --cflags sdl2 2>/dev/null)
|
|
||||||
SDL2_LIBS := $(shell pkg-config --libs sdl2 2>/dev/null)
|
|
||||||
|
|
||||||
# If pkg-config found SDL2, use it
|
|
||||||
ifneq ($(SDL2_CFLAGS),)
|
|
||||||
CFLAGS += $(SDL2_CFLAGS)
|
|
||||||
LIBS = $(SDL2_LIBS) -lSDL2_image -lm
|
|
||||||
endif
|
|
||||||
|
|
||||||
.PHONY: all clean install deps help lint format check memcheck
|
|
||||||
|
|
||||||
all: $(TARGET)
|
|
||||||
|
|
||||||
$(TARGET): $(SOURCE)
|
|
||||||
$(CC) $(CFLAGS) -o $(TARGET) $(SOURCE) $(LIBS)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(TARGET)
|
|
||||||
|
|
||||||
# Linting and code quality targets
|
|
||||||
lint:
|
|
||||||
@echo "Running comprehensive lint checks..."
|
|
||||||
@./lint.sh
|
|
||||||
|
|
||||||
format:
|
|
||||||
@echo "Formatting code..."
|
|
||||||
@if command -v clang-format >/dev/null 2>&1; then \
|
|
||||||
clang-format -i $(SOURCE); \
|
|
||||||
echo "Code formatted successfully"; \
|
|
||||||
else \
|
|
||||||
echo "clang-format not found. Install it with: sudo pacman -S clang"; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
check: lint
|
|
||||||
@echo "Running extended checks..."
|
|
||||||
@if command -v valgrind >/dev/null 2>&1; then \
|
|
||||||
echo "Memory leak check available. Run: make memcheck"; \
|
|
||||||
else \
|
|
||||||
echo "Install valgrind for memory leak detection: sudo pacman -S valgrind"; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
memcheck: $(TARGET)
|
|
||||||
@echo "Running memory leak check..."
|
|
||||||
@valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./$(TARGET) 2>&1 | head -50
|
|
||||||
|
|
||||||
# Install dependencies on Ubuntu/Debian
|
|
||||||
deps-debian:
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install libsdl2-dev libsdl2-image-dev clang-tidy cppcheck clang-format valgrind
|
|
||||||
|
|
||||||
# Install dependencies on Fedora/RHEL/CentOS
|
|
||||||
deps-fedora:
|
|
||||||
sudo dnf install SDL2-devel SDL2_image-devel clang-tools-extra cppcheck clang valgrind
|
|
||||||
|
|
||||||
# Install dependencies on Arch Linux
|
|
||||||
deps-arch:
|
|
||||||
sudo pacman -S sdl2 sdl2_image clang cppcheck valgrind
|
|
||||||
|
|
||||||
# Run with a test image
|
|
||||||
test:
|
|
||||||
@if [ -f $(TARGET) ]; then \
|
|
||||||
echo "Looking for test images..."; \
|
|
||||||
if [ -d "../misc/randomJPG/14k" ] && [ -n "$$(ls ../misc/randomJPG/14k/*.jpg 2>/dev/null | head -1)" ]; then \
|
|
||||||
echo "Running with test image from randomJPG folder..."; \
|
|
||||||
./$(TARGET) $$(ls ../misc/randomJPG/14k/*.jpg | head -1); \
|
|
||||||
else \
|
|
||||||
echo "No test images found. Please run: ./$(TARGET) <image_file.jpg>"; \
|
|
||||||
fi \
|
|
||||||
else \
|
|
||||||
echo "Build the project first: make"; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
help:
|
|
||||||
@echo "imageViewer - Makefile targets:"
|
|
||||||
@echo " all - Build the image viewer"
|
|
||||||
@echo " clean - Remove built files"
|
|
||||||
@echo " test - Run with a test image (if available)"
|
|
||||||
@echo " lint - Run comprehensive code linting"
|
|
||||||
@echo " format - Format code with clang-format"
|
|
||||||
@echo " check - Run all code quality checks"
|
|
||||||
@echo " memcheck - Run memory leak detection"
|
|
||||||
@echo " deps-debian - Install dependencies on Ubuntu/Debian"
|
|
||||||
@echo " deps-fedora - Install dependencies on Fedora/RHEL/CentOS"
|
|
||||||
@echo " deps-arch - Install dependencies on Arch Linux"
|
|
||||||
@echo " help - Show this help"
|
|
||||||
@echo ""
|
|
||||||
@echo "Code Quality Workflow:"
|
|
||||||
@echo " 1. make lint - Check for issues"
|
|
||||||
@echo " 2. make format - Fix formatting"
|
|
||||||
@echo " 3. make check - Run all checks"
|
|
||||||
@echo " 4. make - Build project"
|
|
||||||
@echo " 5. make memcheck - Check for memory leaks"
|
|
||||||
@echo ""
|
|
||||||
@echo "Usage: ./$(TARGET) <image_file.jpg>"
|
|
||||||
@ -1,187 +0,0 @@
|
|||||||
# JPG Image Viewer
|
|
||||||
|
|
||||||
A simple, lightweight image viewer written in C using SDL2. Supports JPG, PNG, BMP, GIF, and TIF formats with zooming, panning, and basic image navigation features.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **Multi-format support**: JPG, JPEG, PNG, BMP, GIF, TIF
|
|
||||||
- **Zoom functionality**: Mouse wheel or keyboard controls
|
|
||||||
- **Pan support**: Click and drag to move around zoomed images
|
|
||||||
- **Auto-fit**: Automatically fits large images to window
|
|
||||||
- **Responsive**: Resizable window with real-time updates
|
|
||||||
- **Keyboard shortcuts**: Quick controls for common operations
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
The image viewer requires SDL2 and SDL2_image libraries.
|
|
||||||
|
|
||||||
### Ubuntu/Debian
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install libsdl2-dev libsdl2-image-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fedora/RHEL/CentOS
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo dnf install SDL2-devel SDL2_image-devel
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arch Linux
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo pacman -S sdl2 sdl2_image
|
|
||||||
```
|
|
||||||
|
|
||||||
### macOS (with Homebrew)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brew install sdl2 sdl2_image
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### Arch Linux (Automated)
|
|
||||||
|
|
||||||
For Arch Linux users, there's an automated installation script that handles everything:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./install_arch.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This script will:
|
|
||||||
|
|
||||||
- Install SDL2 dependencies via pacman
|
|
||||||
- Build the imageviewer from source
|
|
||||||
- Install the binary to `/usr/local/bin`
|
|
||||||
- Create a desktop entry for GUI access
|
|
||||||
- Set up file associations
|
|
||||||
|
|
||||||
To uninstall:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./uninstall_arch.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Manual Building
|
|
||||||
|
|
||||||
1. Install dependencies (see above)
|
|
||||||
2. Build the project:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
make
|
|
||||||
```
|
|
||||||
|
|
||||||
Or use the dependency helper:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
make deps-debian # For Ubuntu/Debian
|
|
||||||
make deps-fedora # For Fedora/RHEL/CentOS
|
|
||||||
make deps-arch # For Arch Linux
|
|
||||||
make
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./imageviewer <image_file>
|
|
||||||
```
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./imageviewer photo.jpg
|
|
||||||
./imageviewer ../misc/randomJPG/14k/bloated_image_1.jpg
|
|
||||||
```
|
|
||||||
|
|
||||||
## Controls
|
|
||||||
|
|
||||||
| Control | Action |
|
|
||||||
| --------------- | ---------------------------------- |
|
|
||||||
| **Mouse wheel** | Zoom in/out |
|
|
||||||
| **+ / -** | Zoom in/out (keyboard) |
|
|
||||||
| **Mouse drag** | Pan around the image |
|
|
||||||
| **R** | Reset zoom and position to default |
|
|
||||||
| **F** | Fit image to window |
|
|
||||||
| **H** | Show help in console |
|
|
||||||
| **ESC / Q** | Quit the application |
|
|
||||||
|
|
||||||
## Features in Detail
|
|
||||||
|
|
||||||
### Zooming
|
|
||||||
|
|
||||||
- Use mouse wheel to zoom in/out at the mouse cursor position
|
|
||||||
- Keyboard shortcuts: `+` to zoom in, `-` to zoom out
|
|
||||||
- Zoom range: 0.1x to 10x
|
|
||||||
- Smart zoom behavior focuses on mouse position
|
|
||||||
|
|
||||||
### Panning
|
|
||||||
|
|
||||||
- Click and drag with left mouse button to move around zoomed images
|
|
||||||
- Smooth panning for precise positioning
|
|
||||||
- Works at any zoom level
|
|
||||||
|
|
||||||
### Auto-fit
|
|
||||||
|
|
||||||
- Large images are automatically scaled to fit the window when first loaded
|
|
||||||
- Press `F` to manually fit the current image to window
|
|
||||||
- Maintains aspect ratio
|
|
||||||
|
|
||||||
### Window Management
|
|
||||||
|
|
||||||
- Resizable window that adapts to content
|
|
||||||
- Real-time rendering updates
|
|
||||||
- Dark background for better image contrast
|
|
||||||
|
|
||||||
## Technical Details
|
|
||||||
|
|
||||||
- **Language**: C (C99 standard)
|
|
||||||
- **Graphics**: SDL2 for window management and rendering
|
|
||||||
- **Image loading**: SDL2_image for multi-format support
|
|
||||||
- **Performance**: Hardware-accelerated rendering when available
|
|
||||||
- **Memory**: Efficient texture management with proper cleanup
|
|
||||||
|
|
||||||
## Makefile Targets
|
|
||||||
|
|
||||||
- `make` or `make all` - Build the image viewer
|
|
||||||
- `make clean` - Remove built files
|
|
||||||
- `make test` - Run with a test image (if available in randomJPG folder)
|
|
||||||
- `make deps-debian` - Install dependencies on Ubuntu/Debian
|
|
||||||
- `make deps-fedora` - Install dependencies on Fedora/RHEL/CentOS
|
|
||||||
- `make deps-arch` - Install dependencies on Arch Linux
|
|
||||||
- `make help` - Show available targets and usage
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### "SDL could not initialize" Error
|
|
||||||
|
|
||||||
Make sure SDL2 development libraries are installed:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Ubuntu/Debian
|
|
||||||
sudo apt-get install libsdl2-dev libsdl2-image-dev
|
|
||||||
|
|
||||||
# Check if libraries are found
|
|
||||||
pkg-config --libs sdl2
|
|
||||||
```
|
|
||||||
|
|
||||||
### "Unable to load image" Error
|
|
||||||
|
|
||||||
- Check that the image file exists and is readable
|
|
||||||
- Verify the image format is supported (JPG, PNG, BMP, GIF, TIF)
|
|
||||||
- Try with a different image file to isolate the issue
|
|
||||||
|
|
||||||
### Compilation Errors
|
|
||||||
|
|
||||||
- Ensure you have a C compiler installed (gcc or clang)
|
|
||||||
- Check that SDL2 headers are available
|
|
||||||
- Try rebuilding with `make clean && make`
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This project is open source. See the LICENSE file for details.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Feel free to submit issues and enhancement requests. The code is designed to be simple and educational while being fully functional.
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
# Security Notes for ImageViewer
|
|
||||||
|
|
||||||
## Static Analysis Warnings
|
|
||||||
|
|
||||||
The imageviewer project uses secure coding practices with proper bounds checking. However, clang-analyzer may report warnings about "insecure" functions like `memcpy` and `snprintf`. These warnings are related to the clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling check.
|
|
||||||
|
|
||||||
### Why These Warnings Appear
|
|
||||||
|
|
||||||
The static analyzer flags standard C library functions like:
|
|
||||||
|
|
||||||
- `memcpy()` - suggests using `memcpy_s()`
|
|
||||||
- `snprintf()` - suggests using `snprintf_s()`
|
|
||||||
- `strncpy()` - suggests using `strncpy_s()`
|
|
||||||
|
|
||||||
### Why These Are Safe in Our Code
|
|
||||||
|
|
||||||
1. **Proper Bounds Checking**: All string operations include explicit length validation before copying
|
|
||||||
2. **Buffer Size Validation**: We check that destination buffers are large enough
|
|
||||||
3. **Null Termination**: All strings are properly null-terminated
|
|
||||||
4. **Return Value Checking**: We validate snprintf return values for buffer overflow detection
|
|
||||||
|
|
||||||
### Example of Secure Usage
|
|
||||||
|
|
||||||
```c
|
|
||||||
// We validate length before copying
|
|
||||||
size_t filename_len = strlen(filename);
|
|
||||||
size_t copy_len = (filename_len < MAX_PATH_LEN - 1) ? filename_len : MAX_PATH_LEN - 1;
|
|
||||||
memcpy(viewer->current_file, filename, copy_len);
|
|
||||||
viewer->current_file[copy_len] = '\0'; // Always null-terminate
|
|
||||||
|
|
||||||
// We check snprintf return value
|
|
||||||
int ret = snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);
|
|
||||||
if (ret < 0 || ret >= sizeof(full_path)) {
|
|
||||||
continue; // Skip if path is too long
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Microsoft-Specific \_s Functions
|
|
||||||
|
|
||||||
The suggested `_s` functions (like `memcpy_s`, `snprintf_s`) are:
|
|
||||||
|
|
||||||
- Microsoft-specific extensions
|
|
||||||
- Not part of standard C
|
|
||||||
- Not portable to Linux/Unix systems
|
|
||||||
- Not available in our build environment
|
|
||||||
|
|
||||||
### Security Assessment
|
|
||||||
|
|
||||||
**Status**: ✅ **SECURE**
|
|
||||||
|
|
||||||
The current implementation is secure because:
|
|
||||||
|
|
||||||
- All buffer operations are bounds-checked
|
|
||||||
- No user input is directly copied without validation
|
|
||||||
- File paths are validated for maximum length
|
|
||||||
- Memory allocation is checked for success
|
|
||||||
- All arrays have defined maximum sizes
|
|
||||||
|
|
||||||
### Suppressing Warnings
|
|
||||||
|
|
||||||
For development, these specific warnings can be suppressed since the code has been manually reviewed for security:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Suppress in clang-tidy configuration
|
|
||||||
-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling
|
|
||||||
```
|
|
||||||
|
|
||||||
Or use NOLINT comments for specific lines:
|
|
||||||
|
|
||||||
```c
|
|
||||||
memcpy(dest, src, len); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Verification
|
|
||||||
|
|
||||||
To verify security:
|
|
||||||
|
|
||||||
1. ✅ All string operations use explicit length checking
|
|
||||||
2. ✅ Buffer overflow conditions are detected and handled
|
|
||||||
3. ✅ No direct user input to buffer operations
|
|
||||||
4. ✅ Static buffers have sufficient size for all use cases
|
|
||||||
5. ✅ Dynamic memory is properly allocated and freed
|
|
||||||
|
|
||||||
This codebase follows secure coding practices and the static analysis warnings are false positives due to the analyzer's conservative approach to C library functions.
|
|
||||||
@ -1,341 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# ImageViewer Installation Script for Arch Linux
|
|
||||||
# This script installs dependencies, builds, and installs the imageviewer
|
|
||||||
|
|
||||||
set -e # Exit on any error
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
INSTALL_DIR="/usr/local/bin"
|
|
||||||
DESKTOP_FILE_DIR="/usr/local/share/applications"
|
|
||||||
ICON_DIR="/usr/local/share/pixmaps"
|
|
||||||
|
|
||||||
print_step() {
|
|
||||||
echo -e "${BLUE}==>${NC} ${1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_success() {
|
|
||||||
echo -e "${GREEN}✓${NC} ${1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_warning() {
|
|
||||||
echo -e "${YELLOW}⚠${NC} ${1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_error() {
|
|
||||||
echo -e "${RED}✗${NC} ${1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
check_arch() {
|
|
||||||
if [[ ! -f /etc/arch-release ]]; then
|
|
||||||
print_error "This script is designed for Arch Linux only."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
print_success "Arch Linux detected"
|
|
||||||
}
|
|
||||||
|
|
||||||
check_permissions() {
|
|
||||||
if [[ $EUID -eq 0 ]]; then
|
|
||||||
print_warning "Running as root. This script should be run as a regular user."
|
|
||||||
print_warning "It will prompt for sudo when needed."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
install_dependencies() {
|
|
||||||
print_step "Checking dependencies..."
|
|
||||||
|
|
||||||
# Check if pacman is available
|
|
||||||
if ! command -v pacman &> /dev/null; then
|
|
||||||
print_error "pacman not found. Are you sure this is Arch Linux?"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Define required packages and show what we will check
|
|
||||||
local packages=("sdl2" "sdl2_image" "gcc" "make" "pkg-config" "xdg-utils")
|
|
||||||
print_step "Packages to verify: ${packages[*]}"
|
|
||||||
|
|
||||||
# Determine missing packages in a single call (faster than looping)
|
|
||||||
# pacman -T lists targets that are not currently installed
|
|
||||||
local missing_output
|
|
||||||
missing_output=$(pacman -T "${packages[@]}" 2>/dev/null || true)
|
|
||||||
|
|
||||||
# Build arrays for missing and installed for better UX
|
|
||||||
local missing_packages=()
|
|
||||||
local installed_packages=()
|
|
||||||
if [[ -n "$missing_output" ]]; then
|
|
||||||
# Populate missing_packages from pacman -T output
|
|
||||||
while IFS= read -r line; do
|
|
||||||
[[ -n "$line" ]] && missing_packages+=("$line")
|
|
||||||
done <<< "$missing_output"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Derive installed packages by set subtraction
|
|
||||||
for pkg in "${packages[@]}"; do
|
|
||||||
local found_missing=0
|
|
||||||
for miss in "${missing_packages[@]}"; do
|
|
||||||
if [[ "$pkg" == "$miss" ]]; then
|
|
||||||
found_missing=1
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [[ $found_missing -eq 0 ]]; then
|
|
||||||
installed_packages+=("$pkg")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Verbose summary
|
|
||||||
if [[ ${#installed_packages[@]} -gt 0 ]]; then
|
|
||||||
print_success "Already installed: ${installed_packages[*]}"
|
|
||||||
fi
|
|
||||||
if [[ ${#missing_packages[@]} -eq 0 ]]; then
|
|
||||||
print_success "All dependencies are already installed"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
print_warning "Missing packages: ${missing_packages[*]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_step "Updating package database..."
|
|
||||||
sudo pacman -Sy
|
|
||||||
|
|
||||||
print_step "Installing missing dependencies..."
|
|
||||||
sudo pacman -S --needed "${missing_packages[@]}"
|
|
||||||
|
|
||||||
print_success "Dependencies installed successfully"
|
|
||||||
}
|
|
||||||
|
|
||||||
build_imageviewer() {
|
|
||||||
print_step "Building imageviewer..."
|
|
||||||
|
|
||||||
# Check if we're in the right directory
|
|
||||||
if [[ ! -f "main.c" ]] || [[ ! -f "Makefile" ]]; then
|
|
||||||
print_error "main.c or Makefile not found. Please run this script from the imageViewer directory."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean any previous builds
|
|
||||||
make clean 2>/dev/null || true
|
|
||||||
|
|
||||||
# Build the project
|
|
||||||
if make; then
|
|
||||||
print_success "Build completed successfully"
|
|
||||||
else
|
|
||||||
print_error "Build failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Verify the binary was created
|
|
||||||
if [[ ! -f "imageviewer" ]]; then
|
|
||||||
print_error "imageviewer binary not found after build"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_success "imageviewer binary created"
|
|
||||||
}
|
|
||||||
|
|
||||||
install_binary() {
|
|
||||||
print_step "Installing imageviewer to ${INSTALL_DIR}..."
|
|
||||||
|
|
||||||
# Create install directory if it doesn't exist
|
|
||||||
sudo mkdir -p "${INSTALL_DIR}"
|
|
||||||
|
|
||||||
# Copy the binary
|
|
||||||
sudo cp imageviewer "${INSTALL_DIR}/"
|
|
||||||
sudo chmod +x "${INSTALL_DIR}/imageviewer"
|
|
||||||
|
|
||||||
print_success "imageviewer installed to ${INSTALL_DIR}/imageviewer"
|
|
||||||
}
|
|
||||||
|
|
||||||
create_desktop_entry() {
|
|
||||||
print_step "Creating desktop entry..."
|
|
||||||
|
|
||||||
# Create applications directory if it doesn't exist
|
|
||||||
sudo mkdir -p "${DESKTOP_FILE_DIR}"
|
|
||||||
|
|
||||||
# Create desktop file
|
|
||||||
sudo tee "${DESKTOP_FILE_DIR}/imageviewer.desktop" > /dev/null << EOF
|
|
||||||
[Desktop Entry]
|
|
||||||
Version=1.0
|
|
||||||
Type=Application
|
|
||||||
Name=Image Viewer
|
|
||||||
Comment=Simple SDL2-based image viewer
|
|
||||||
Exec=imageviewer %f
|
|
||||||
Icon=imageviewer
|
|
||||||
Terminal=false
|
|
||||||
MimeType=image/jpeg;image/jpg;image/png;image/bmp;image/gif;image/tiff;image/tif;image/webp;
|
|
||||||
Categories=Graphics;Photography;Viewer;
|
|
||||||
StartupNotify=true
|
|
||||||
NoDisplay=false
|
|
||||||
EOF
|
|
||||||
|
|
||||||
print_success "Desktop entry created"
|
|
||||||
}
|
|
||||||
|
|
||||||
create_simple_icon() {
|
|
||||||
print_step "Creating application icon..."
|
|
||||||
|
|
||||||
# Create icon directory if it doesn't exist
|
|
||||||
sudo mkdir -p "${ICON_DIR}"
|
|
||||||
|
|
||||||
# Create a simple text-based icon (SVG)
|
|
||||||
sudo tee "${ICON_DIR}/imageviewer.svg" > /dev/null << 'EOF'
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect width="48" height="48" fill="#2E3440" rx="4"/>
|
|
||||||
<rect x="6" y="6" width="36" height="36" fill="#3B4252" rx="2"/>
|
|
||||||
<rect x="10" y="10" width="28" height="20" fill="#4C566A" rx="1"/>
|
|
||||||
<circle cx="16" cy="18" r="3" fill="#EBCB8B"/>
|
|
||||||
<polygon points="10,25 18,17 22,21 30,13 38,21 38,30 10,30" fill="#81A1C1"/>
|
|
||||||
<rect x="10" y="32" width="28" height="6" fill="#5E81AC" rx="1"/>
|
|
||||||
</svg>
|
|
||||||
EOF
|
|
||||||
|
|
||||||
print_success "Application icon created"
|
|
||||||
}
|
|
||||||
|
|
||||||
update_desktop_database() {
|
|
||||||
print_step "Updating desktop database..."
|
|
||||||
|
|
||||||
if command -v update-desktop-database &> /dev/null; then
|
|
||||||
sudo update-desktop-database "${DESKTOP_FILE_DIR}" 2>/dev/null || true
|
|
||||||
print_success "Desktop database updated"
|
|
||||||
else
|
|
||||||
print_warning "update-desktop-database not found, skipping..."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
set_default_image_viewer() {
|
|
||||||
print_step "Setting imageviewer as default image viewer..."
|
|
||||||
|
|
||||||
# List of MIME types for images
|
|
||||||
local mime_types=(
|
|
||||||
"image/jpeg"
|
|
||||||
"image/jpg"
|
|
||||||
"image/png"
|
|
||||||
"image/bmp"
|
|
||||||
"image/gif"
|
|
||||||
"image/tiff"
|
|
||||||
"image/tif"
|
|
||||||
"image/webp"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set default application for each MIME type
|
|
||||||
for mime_type in "${mime_types[@]}"; do
|
|
||||||
if command -v xdg-mime &> /dev/null; then
|
|
||||||
xdg-mime default imageviewer.desktop "$mime_type" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Also update MIME database if available
|
|
||||||
if command -v update-mime-database &> /dev/null; then
|
|
||||||
sudo update-mime-database /usr/share/mime 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_success "imageviewer set as default image viewer"
|
|
||||||
}
|
|
||||||
|
|
||||||
test_installation() {
|
|
||||||
print_step "Testing installation..."
|
|
||||||
|
|
||||||
# Check if binary is in PATH
|
|
||||||
if command -v imageviewer &> /dev/null; then
|
|
||||||
print_success "imageviewer is available in PATH"
|
|
||||||
|
|
||||||
# Show version/help
|
|
||||||
echo -e "${BLUE}Running imageviewer --help equivalent:${NC}"
|
|
||||||
echo "Usage: imageviewer <image_file_or_directory>"
|
|
||||||
echo "Supported formats: JPG, JPEG, PNG, BMP, GIF, TIF"
|
|
||||||
|
|
||||||
# Test default application association
|
|
||||||
if command -v xdg-mime &> /dev/null; then
|
|
||||||
local default_app
|
|
||||||
default_app=$(xdg-mime query default image/jpeg 2>/dev/null)
|
|
||||||
if [[ "$default_app" == "imageviewer.desktop" ]]; then
|
|
||||||
print_success "imageviewer is set as default image viewer"
|
|
||||||
else
|
|
||||||
print_warning "Default image viewer association may not have been set correctly"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
print_error "imageviewer not found in PATH. Installation may have failed."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
print_step "Cleaning up build files..."
|
|
||||||
make clean 2>/dev/null || true
|
|
||||||
print_success "Cleanup completed"
|
|
||||||
}
|
|
||||||
|
|
||||||
show_usage_info() {
|
|
||||||
echo
|
|
||||||
echo -e "${GREEN}Installation completed successfully!${NC}"
|
|
||||||
echo
|
|
||||||
echo -e "${BLUE}Usage:${NC}"
|
|
||||||
echo " imageviewer <image_file_or_directory>"
|
|
||||||
echo " Or simply double-click on image files (now set as default viewer)"
|
|
||||||
echo
|
|
||||||
echo -e "${BLUE}Examples:${NC}"
|
|
||||||
echo " imageviewer photo.jpg"
|
|
||||||
echo " imageviewer /path/to/image/directory"
|
|
||||||
echo
|
|
||||||
echo -e "${BLUE}Controls:${NC}"
|
|
||||||
echo " Mouse wheel / +/- : Zoom in/out"
|
|
||||||
echo " Mouse drag : Pan image"
|
|
||||||
echo " Left/Right Arrow : Navigate between images"
|
|
||||||
echo " Hold Left/Right : Auto-navigate"
|
|
||||||
echo " R : Reset zoom and position"
|
|
||||||
echo " F : Fit image to window"
|
|
||||||
echo " H : Show help"
|
|
||||||
echo " ESC/Q : Quit"
|
|
||||||
echo
|
|
||||||
echo -e "${BLUE}Default Image Viewer:${NC}"
|
|
||||||
echo " imageviewer is now set as the default application for:"
|
|
||||||
echo " JPG, JPEG, PNG, BMP, GIF, TIFF, TIF, WEBP files"
|
|
||||||
echo
|
|
||||||
echo -e "${BLUE}Uninstall:${NC}"
|
|
||||||
echo " To remove imageviewer, run:"
|
|
||||||
echo " sudo rm ${INSTALL_DIR}/imageviewer"
|
|
||||||
echo " sudo rm ${DESKTOP_FILE_DIR}/imageviewer.desktop"
|
|
||||||
echo " sudo rm ${ICON_DIR}/imageviewer.svg"
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
|
||||||
echo -e "${BLUE}ImageViewer Installation Script for Arch Linux${NC}"
|
|
||||||
echo "=============================================="
|
|
||||||
echo
|
|
||||||
|
|
||||||
check_arch
|
|
||||||
check_permissions
|
|
||||||
|
|
||||||
# Show what the script will do
|
|
||||||
echo -e "${YELLOW}This script will:${NC}"
|
|
||||||
echo " 1. Install SDL2 dependencies via pacman"
|
|
||||||
echo " 2. Build the imageviewer from source"
|
|
||||||
echo " 3. Install the binary to ${INSTALL_DIR}"
|
|
||||||
echo " 4. Create a desktop entry"
|
|
||||||
echo " 5. Set imageviewer as default image viewer"
|
|
||||||
echo
|
|
||||||
|
|
||||||
install_dependencies
|
|
||||||
build_imageviewer
|
|
||||||
install_binary
|
|
||||||
create_desktop_entry
|
|
||||||
create_simple_icon
|
|
||||||
update_desktop_database
|
|
||||||
set_default_image_viewer
|
|
||||||
test_installation
|
|
||||||
cleanup
|
|
||||||
show_usage_info
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run main function
|
|
||||||
main "$@"
|
|
||||||
@ -1,202 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Lint script for imageViewer project
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
print_step() {
|
|
||||||
echo -e "${BLUE}==>${NC} ${1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_success() {
|
|
||||||
echo -e "${GREEN}✓${NC} ${1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_warning() {
|
|
||||||
echo -e "${YELLOW}⚠${NC} ${1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_error() {
|
|
||||||
echo -e "${RED}✗${NC} ${1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if required tools are installed
|
|
||||||
check_tools() {
|
|
||||||
print_step "Checking required tools..."
|
|
||||||
|
|
||||||
local missing_tools=()
|
|
||||||
|
|
||||||
if ! command -v clang-tidy &> /dev/null; then
|
|
||||||
missing_tools+=("clang-tidy")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v cppcheck &> /dev/null; then
|
|
||||||
missing_tools+=("cppcheck")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v clang-format &> /dev/null; then
|
|
||||||
missing_tools+=("clang-format")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ${#missing_tools[@]} -ne 0 ]; then
|
|
||||||
print_error "Missing required tools: ${missing_tools[*]}"
|
|
||||||
print_step "Installing missing tools..."
|
|
||||||
|
|
||||||
# Check if we're on Arch Linux
|
|
||||||
if command -v pacman &> /dev/null; then
|
|
||||||
sudo pacman -S --needed clang cppcheck
|
|
||||||
elif command -v apt &> /dev/null; then
|
|
||||||
sudo apt update && sudo apt install -y clang-tidy cppcheck clang-format
|
|
||||||
elif command -v dnf &> /dev/null; then
|
|
||||||
sudo dnf install -y clang-tools-extra cppcheck clang
|
|
||||||
else
|
|
||||||
print_error "Please install the following tools manually: ${missing_tools[*]}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_success "All required tools are available"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run clang-tidy
|
|
||||||
run_clang_tidy() {
|
|
||||||
print_step "Running clang-tidy analysis..."
|
|
||||||
|
|
||||||
if [ -f ".clang-tidy" ]; then
|
|
||||||
clang-tidy main.c -- -I/usr/include/SDL2 -D_REENTRANT 2>/dev/null || {
|
|
||||||
print_warning "clang-tidy found issues (see output above)"
|
|
||||||
}
|
|
||||||
else
|
|
||||||
print_warning ".clang-tidy config not found, using default settings"
|
|
||||||
clang-tidy main.c -- -I/usr/include/SDL2 -D_REENTRANT 2>/dev/null || {
|
|
||||||
print_warning "clang-tidy found issues (see output above)"
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_success "clang-tidy analysis completed"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run cppcheck
|
|
||||||
run_cppcheck() {
|
|
||||||
print_step "Running cppcheck analysis..."
|
|
||||||
|
|
||||||
cppcheck --enable=all --check-level=exhaustive --suppress=missingIncludeSystem \
|
|
||||||
--quiet --std=c23 main.c || {
|
|
||||||
print_warning "cppcheck found issues (see output above)"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_success "cppcheck analysis completed"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check code formatting
|
|
||||||
check_formatting() {
|
|
||||||
print_step "Checking code formatting..."
|
|
||||||
|
|
||||||
if [ -f ".clang-format" ]; then
|
|
||||||
if clang-format --dry-run --Werror main.c 2>/dev/null; then
|
|
||||||
print_success "Code formatting is correct"
|
|
||||||
else
|
|
||||||
print_warning "Code formatting issues found"
|
|
||||||
echo "Run 'clang-format -i main.c' to fix formatting"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
print_warning ".clang-format config not found, skipping format check"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run basic compile check
|
|
||||||
compile_check() {
|
|
||||||
print_step "Running compile check..."
|
|
||||||
|
|
||||||
# Try to compile with extra warnings
|
|
||||||
local sdl_cflags
|
|
||||||
sdl_cflags=$(pkg-config --cflags sdl2 2>/dev/null || echo "-I/usr/include/SDL2")
|
|
||||||
if gcc -Wall -Wextra -Wpedantic -std=c99 -O2 \
|
|
||||||
${sdl_cflags} \
|
|
||||||
-c main.c -o /tmp/main.o 2>/dev/null; then
|
|
||||||
print_success "Compile check passed"
|
|
||||||
rm -f /tmp/main.o
|
|
||||||
else
|
|
||||||
print_error "Compile check failed"
|
|
||||||
print_step "Trying compile with detailed errors..."
|
|
||||||
gcc -Wall -Wextra -Wpedantic -std=c99 -O2 \
|
|
||||||
${sdl_cflags} \
|
|
||||||
-c main.c -o /tmp/main.o
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check for common C issues
|
|
||||||
check_common_issues() {
|
|
||||||
print_step "Checking for common C issues..."
|
|
||||||
|
|
||||||
local issues=0
|
|
||||||
|
|
||||||
# Check for TODO/FIXME comments
|
|
||||||
if grep -n "TODO\|FIXME\|XXX\|HACK" main.c 2>/dev/null; then
|
|
||||||
print_warning "Found TODO/FIXME comments"
|
|
||||||
issues=$((issues + 1))
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check for potential buffer overflows
|
|
||||||
if grep -n "strcpy\|strcat\|sprintf\|gets" main.c 2>/dev/null; then
|
|
||||||
print_warning "Found potentially unsafe string functions"
|
|
||||||
issues=$((issues + 1))
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check for magic numbers (basic check)
|
|
||||||
if grep -E "\b[0-9]{3,}\b" main.c | grep -v "printf\|#define" 2>/dev/null; then
|
|
||||||
print_warning "Found potential magic numbers"
|
|
||||||
issues=$((issues + 1))
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $issues -eq 0 ]; then
|
|
||||||
print_success "No common issues found"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main execution
|
|
||||||
main() {
|
|
||||||
echo -e "${BLUE}C Language Linter for imageViewer Project${NC}"
|
|
||||||
echo "=========================================="
|
|
||||||
echo
|
|
||||||
|
|
||||||
# Check if we're in the right directory
|
|
||||||
if [ ! -f "main.c" ]; then
|
|
||||||
print_error "main.c not found. Please run this script from the imageViewer directory."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
check_tools
|
|
||||||
echo
|
|
||||||
|
|
||||||
compile_check
|
|
||||||
echo
|
|
||||||
|
|
||||||
run_clang_tidy
|
|
||||||
echo
|
|
||||||
|
|
||||||
run_cppcheck
|
|
||||||
echo
|
|
||||||
|
|
||||||
check_formatting
|
|
||||||
echo
|
|
||||||
|
|
||||||
check_common_issues
|
|
||||||
echo
|
|
||||||
|
|
||||||
print_success "Linting completed!"
|
|
||||||
echo
|
|
||||||
echo -e "${BLUE}Available commands:${NC}"
|
|
||||||
echo " ./lint.sh - Run all checks"
|
|
||||||
echo " clang-format -i main.c - Fix formatting"
|
|
||||||
echo " clang-tidy main.c --fix - Apply clang-tidy fixes"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run main function
|
|
||||||
main "$@"
|
|
||||||
1418
C/imageViewer/main.c
1418
C/imageViewer/main.c
File diff suppressed because it is too large
Load Diff
@ -1,15 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
# Install dependencies
|
|
||||||
if command -v apt-get &>/dev/null; then
|
|
||||||
sudo apt-get install -y libsdl2-dev libsdl2-image-dev
|
|
||||||
elif command -v pacman &>/dev/null; then
|
|
||||||
pacman -Q sdl2 sdl2_image &>/dev/null || sudo pacman -S --noconfirm sdl2 sdl2_image
|
|
||||||
elif command -v dnf &>/dev/null; then
|
|
||||||
sudo dnf install -y SDL2-devel SDL2_image-devel
|
|
||||||
fi
|
|
||||||
make
|
|
||||||
echo "Usage: ./imageviewer <image_file>"
|
|
||||||
if [[ $# -gt 0 ]]; then
|
|
||||||
./imageviewer "$@"
|
|
||||||
fi
|
|
||||||
@ -1,134 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# ImageViewer Uninstallation Script for Arch Linux
|
|
||||||
|
|
||||||
set -e # Exit on any error
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
INSTALL_DIR="/usr/local/bin"
|
|
||||||
DESKTOP_FILE_DIR="/usr/local/share/applications"
|
|
||||||
ICON_DIR="/usr/local/share/pixmaps"
|
|
||||||
|
|
||||||
print_step() {
|
|
||||||
echo -e "${BLUE}==>${NC} ${1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_success() {
|
|
||||||
echo -e "${GREEN}✓${NC} ${1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_warning() {
|
|
||||||
echo -e "${YELLOW}⚠${NC} ${1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_error() {
|
|
||||||
echo -e "${RED}✗${NC} ${1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
remove_files() {
|
|
||||||
print_step "Removing imageviewer files..."
|
|
||||||
|
|
||||||
# Remove binary
|
|
||||||
if [[ -f "${INSTALL_DIR}/imageviewer" ]]; then
|
|
||||||
sudo rm "${INSTALL_DIR}/imageviewer"
|
|
||||||
print_success "Removed ${INSTALL_DIR}/imageviewer"
|
|
||||||
else
|
|
||||||
print_warning "Binary not found at ${INSTALL_DIR}/imageviewer"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Remove desktop entry
|
|
||||||
if [[ -f "${DESKTOP_FILE_DIR}/imageviewer.desktop" ]]; then
|
|
||||||
sudo rm "${DESKTOP_FILE_DIR}/imageviewer.desktop"
|
|
||||||
print_success "Removed desktop entry"
|
|
||||||
else
|
|
||||||
print_warning "Desktop entry not found"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Remove icon
|
|
||||||
if [[ -f "${ICON_DIR}/imageviewer.svg" ]]; then
|
|
||||||
sudo rm "${ICON_DIR}/imageviewer.svg"
|
|
||||||
print_success "Removed application icon"
|
|
||||||
else
|
|
||||||
print_warning "Application icon not found"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
reset_default_associations() {
|
|
||||||
print_step "Resetting default image viewer associations..."
|
|
||||||
|
|
||||||
# List of MIME types for images
|
|
||||||
local mime_types=(
|
|
||||||
"image/jpeg"
|
|
||||||
"image/jpg"
|
|
||||||
"image/png"
|
|
||||||
"image/bmp"
|
|
||||||
"image/gif"
|
|
||||||
"image/tiff"
|
|
||||||
"image/tif"
|
|
||||||
"image/webp"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Reset default application for each MIME type
|
|
||||||
for mime_type in "${mime_types[@]}"; do
|
|
||||||
if command -v xdg-mime &> /dev/null; then
|
|
||||||
# Check if imageviewer was the default
|
|
||||||
local current_default
|
|
||||||
current_default=$(xdg-mime query default "$mime_type" 2>/dev/null)
|
|
||||||
if [[ "$current_default" == "imageviewer.desktop" ]]; then
|
|
||||||
# Remove the association (this will fall back to system defaults)
|
|
||||||
local mimeapps_file="$HOME/.config/mimeapps.list"
|
|
||||||
if [[ -f "$mimeapps_file" ]]; then
|
|
||||||
sed -i "/^${mime_type}=imageviewer.desktop$/d" "$mimeapps_file" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
print_success "Default image viewer associations reset"
|
|
||||||
}
|
|
||||||
|
|
||||||
update_desktop_database() {
|
|
||||||
print_step "Updating desktop database..."
|
|
||||||
|
|
||||||
if command -v update-desktop-database &> /dev/null; then
|
|
||||||
sudo update-desktop-database "${DESKTOP_FILE_DIR}" 2>/dev/null || true
|
|
||||||
print_success "Desktop database updated"
|
|
||||||
else
|
|
||||||
print_warning "update-desktop-database not found, skipping..."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
|
||||||
echo -e "${BLUE}ImageViewer Uninstallation Script${NC}"
|
|
||||||
echo "================================="
|
|
||||||
echo
|
|
||||||
|
|
||||||
# Show what will be removed
|
|
||||||
echo -e "${YELLOW}This script will remove:${NC}"
|
|
||||||
echo " - ${INSTALL_DIR}/imageviewer"
|
|
||||||
echo " - ${DESKTOP_FILE_DIR}/imageviewer.desktop"
|
|
||||||
echo " - ${ICON_DIR}/imageviewer.svg"
|
|
||||||
echo
|
|
||||||
echo -e "${YELLOW}Note: Dependencies (SDL2 libraries) will NOT be removed.${NC}"
|
|
||||||
echo
|
|
||||||
|
|
||||||
remove_files
|
|
||||||
reset_default_associations
|
|
||||||
update_desktop_database
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo -e "${GREEN}ImageViewer has been successfully uninstalled!${NC}"
|
|
||||||
echo
|
|
||||||
echo -e "${BLUE}To remove dependencies (if no longer needed):${NC}"
|
|
||||||
echo " sudo pacman -R sdl2 sdl2_image"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run main function
|
|
||||||
main "$@"
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
CC := gcc
|
|
||||||
CFLAGS := -O2 -std=c11 -Wall -Wextra -Wno-unused-parameter
|
|
||||||
COV := -O0 -g --coverage -std=c11 -Wall -Wextra -Wno-unused-parameter -Wno-return-type
|
|
||||||
LDFLAGS :=
|
|
||||||
|
|
||||||
SRC := main.c movegen.c search.c
|
|
||||||
BIN := random_engine
|
|
||||||
|
|
||||||
# Perft driver
|
|
||||||
PERFT_SRC := perft.c movegen.c
|
|
||||||
PERFT_BIN := perft
|
|
||||||
|
|
||||||
.PHONY: all clean rebuild test coverage
|
|
||||||
|
|
||||||
all: $(BIN)
|
|
||||||
|
|
||||||
$(BIN): $(SRC)
|
|
||||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
$(PERFT_BIN): $(PERFT_SRC)
|
|
||||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
# ---- tests ------------------------------------------------------------------
|
|
||||||
|
|
||||||
test_movegen: test_movegen.c movegen.c movegen.h
|
|
||||||
$(CC) $(COV) -o test_movegen test_movegen.c movegen.c
|
|
||||||
|
|
||||||
test_search: test_search.c search.c movegen.c movegen.h search.h
|
|
||||||
$(CC) $(COV) -o test_search test_search.c search.c movegen.c
|
|
||||||
|
|
||||||
test: test_movegen test_search
|
|
||||||
./test_movegen
|
|
||||||
./test_search
|
|
||||||
|
|
||||||
# ---- coverage ---------------------------------------------------------------
|
|
||||||
|
|
||||||
coverage: test_movegen test_search
|
|
||||||
./test_movegen
|
|
||||||
./test_search
|
|
||||||
lcov --capture --directory . --output-file coverage.info \
|
|
||||||
--rc branch_coverage=1 --ignore-errors inconsistent,mismatch,unused
|
|
||||||
lcov --extract coverage.info \
|
|
||||||
"$(CURDIR)/movegen.c" "$(CURDIR)/search.c" \
|
|
||||||
--output-file coverage.info \
|
|
||||||
--ignore-errors unused,inconsistent
|
|
||||||
@echo "--- Coverage Summary ---"
|
|
||||||
lcov --summary coverage.info --rc branch_coverage=1 2>&1 | tee /tmp/lcov_engine_summary.txt
|
|
||||||
@LINE_COV=$$(grep "lines" /tmp/lcov_engine_summary.txt | grep -oP '[0-9]+\.[0-9]+(?=%)'); \
|
|
||||||
echo "Line coverage: $${LINE_COV}%"; \
|
|
||||||
if [ "$$(echo "$${LINE_COV} < 100.0" | bc)" = "1" ]; then \
|
|
||||||
echo "FAIL: line coverage below 100%"; exit 1; \
|
|
||||||
fi
|
|
||||||
@echo "OK: 100% line coverage achieved"
|
|
||||||
|
|
||||||
run: $(BIN)
|
|
||||||
./$(BIN)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(BIN) $(PERFT_BIN) test_movegen test_search \
|
|
||||||
*.gcda *.gcno *.gcov coverage.info
|
|
||||||
|
|
||||||
rebuild: clean all
|
|
||||||
|
|
||||||
.PHONY: perft
|
|
||||||
perft: $(PERFT_BIN)
|
|
||||||
@ -1,249 +0,0 @@
|
|||||||
// Minimal chess engine CLI: random fallback + alpha-beta search for provided move list
|
|
||||||
// Contract expected by PYTHON/lichess_bot/engine.py:
|
|
||||||
// - Usage without explanation: random_engine --fen "<FEN>" <uci1> <uci2> ...
|
|
||||||
// -> prints the chosen UCI move on stdout
|
|
||||||
// - With explanation: random_engine --fen "<FEN>" --explain [--analyze <uci>] <uci1>
|
|
||||||
// <uci2> ...
|
|
||||||
// -> prints a compact JSON object containing chosen_move and a simple analyze block
|
|
||||||
//
|
|
||||||
// Notes:
|
|
||||||
// - We don't validate or parse FEN yet; it's accepted for future use.
|
|
||||||
// - We choose a uniformly random move among the provided UCIs.
|
|
||||||
// - For "--analyze" the candidate score is a placeholder (0.0) for now.
|
|
||||||
|
|
||||||
#include "movegen.h"
|
|
||||||
#include "search.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
const char *fen;
|
|
||||||
int explain;
|
|
||||||
const char *analyze_move;
|
|
||||||
const char **moves;
|
|
||||||
int move_count;
|
|
||||||
} Args;
|
|
||||||
|
|
||||||
static void print_usage(const char *prog)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Usage: %s --fen '<FEN>' [--explain] [--analyze <uci>] <uci_moves...>\n", prog);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int parse_args(int argc, char **argv, Args *out)
|
|
||||||
{
|
|
||||||
memset(out, 0, sizeof(*out));
|
|
||||||
out->moves = NULL;
|
|
||||||
out->move_count = 0;
|
|
||||||
|
|
||||||
// Collect options regardless of order; every non-option token is a move.
|
|
||||||
const char **moves = NULL;
|
|
||||||
int moves_cap = 0;
|
|
||||||
int moves_len = 0;
|
|
||||||
|
|
||||||
for (int i = 1; i < argc; ++i)
|
|
||||||
{
|
|
||||||
const char *a = argv[i];
|
|
||||||
if (strcmp(a, "--fen") == 0)
|
|
||||||
{
|
|
||||||
if (i + 1 >= argc)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "--fen requires an argument\n");
|
|
||||||
free(moves);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
out->fen = argv[++i];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strcmp(a, "--explain") == 0)
|
|
||||||
{
|
|
||||||
out->explain = 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (strcmp(a, "--analyze") == 0)
|
|
||||||
{
|
|
||||||
if (i + 1 >= argc)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "--analyze requires a UCI move\n");
|
|
||||||
free(moves);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
out->analyze_move = argv[++i];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Otherwise treat as move
|
|
||||||
if (moves_len >= moves_cap)
|
|
||||||
{
|
|
||||||
int new_cap = moves_cap == 0 ? 8 : moves_cap * 2;
|
|
||||||
const char **tmp =
|
|
||||||
(const char **)realloc(moves, (size_t)new_cap * sizeof(const char *));
|
|
||||||
if (!tmp)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Out of memory\n");
|
|
||||||
free(moves);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
moves = tmp;
|
|
||||||
moves_cap = new_cap;
|
|
||||||
}
|
|
||||||
moves[moves_len++] = a;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!out->fen)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Missing --fen argument\n");
|
|
||||||
free(moves);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (moves_len > 0)
|
|
||||||
{
|
|
||||||
out->moves = moves; // keep ownership until program end
|
|
||||||
out->move_count = moves_len;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
free(moves);
|
|
||||||
out->moves = NULL;
|
|
||||||
out->move_count = 0;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int pick_random_index(int n, const char *fen)
|
|
||||||
{
|
|
||||||
if (n <= 0)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
// Mix in time and a simple FEN hash for a touch of variety/repeatability.
|
|
||||||
unsigned long hash = 1469598103934665603ULL; // FNV offset basis
|
|
||||||
if (fen)
|
|
||||||
{
|
|
||||||
const unsigned char *p = (const unsigned char *)fen;
|
|
||||||
while (*p)
|
|
||||||
{
|
|
||||||
hash ^= (unsigned long)(*p++);
|
|
||||||
hash *= 1099511628211ULL; // FNV prime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unsigned long seed = (unsigned long)time(NULL) ^ hash;
|
|
||||||
srand((unsigned int)(seed ^ (seed >> 32)));
|
|
||||||
int idx = rand() % n;
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int find_best_move_from_ucis(const char **ucis, int n_ucis, const char *fen, int depth,
|
|
||||||
int *out_index)
|
|
||||||
{
|
|
||||||
Position pos;
|
|
||||||
if (!parse_fen(&pos, fen))
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// Convert UCI list into legal moves vetted by our generator, but preserve provided order as
|
|
||||||
// fallback
|
|
||||||
Move legal[256];
|
|
||||||
int map_idx[256];
|
|
||||||
int L = 0;
|
|
||||||
for (int i = 0; i < n_ucis; i++)
|
|
||||||
{
|
|
||||||
Move m;
|
|
||||||
if (move_from_uci(&pos, ucis[i], &m))
|
|
||||||
{
|
|
||||||
legal[L] = m;
|
|
||||||
map_idx[L] = i;
|
|
||||||
L++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (L == 0)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int best_idx = 0;
|
|
||||||
int best_score = -2147483647;
|
|
||||||
for (int i = 0; i < L; i++)
|
|
||||||
{
|
|
||||||
Position child = pos;
|
|
||||||
Piece cap = EMPTY;
|
|
||||||
make_move(&child, &legal[i], &cap);
|
|
||||||
PrincipalVariation pv = {.from = -1, .to = -1};
|
|
||||||
int score = -alphabeta(child, depth - 1, -30000, 30000, &pv);
|
|
||||||
if (score > best_score)
|
|
||||||
{
|
|
||||||
best_score = score;
|
|
||||||
best_idx = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Map best move back to index in original ucis list using map_idx
|
|
||||||
if (out_index)
|
|
||||||
{
|
|
||||||
*out_index = map_idx[best_idx];
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
Args args;
|
|
||||||
if (!parse_args(argc, argv, &args))
|
|
||||||
{
|
|
||||||
print_usage(argv[0]);
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.move_count <= 0)
|
|
||||||
{
|
|
||||||
// No legal moves provided; output nothing to keep contract simple.
|
|
||||||
if (args.explain)
|
|
||||||
{
|
|
||||||
// Still return a valid JSON object for callers expecting it.
|
|
||||||
printf("{\"chosen_index\":-1,\"chosen_move\":\"\",\"analyze\":{\"candidate_move\":\"%"
|
|
||||||
"s\",\"candidate_score\":0.0}}\n",
|
|
||||||
args.analyze_move ? args.analyze_move : "");
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a FEN and move list, run a shallow alpha-beta to choose among provided moves.
|
|
||||||
int chosen_idx = -1;
|
|
||||||
if (args.fen && args.move_count > 0)
|
|
||||||
{
|
|
||||||
if (!find_best_move_from_ucis(args.moves, args.move_count, args.fen, 3, &chosen_idx))
|
|
||||||
{
|
|
||||||
chosen_idx = pick_random_index(args.move_count, args.fen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
chosen_idx = pick_random_index(args.move_count, args.fen);
|
|
||||||
}
|
|
||||||
if (chosen_idx < 0 || chosen_idx >= args.move_count)
|
|
||||||
{
|
|
||||||
(void)fprintf(stderr, "Internal error picking move index\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
const char *chosen = args.moves[chosen_idx];
|
|
||||||
|
|
||||||
if (!args.explain)
|
|
||||||
{
|
|
||||||
printf("%s\n", chosen);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Minimal JSON explanation compatible with engine.py's parser
|
|
||||||
// Fields consumed by Python wrapper:
|
|
||||||
// - chosen_move (string)
|
|
||||||
// - chosen_index (int)
|
|
||||||
// - analyze.candidate_score (number) [optional but provided]
|
|
||||||
// Additionally include analyze.candidate_move for easier debugging.
|
|
||||||
const char *cand = args.analyze_move ? args.analyze_move : "";
|
|
||||||
double cand_score = 0.0; // placeholder; real eval will come later
|
|
||||||
|
|
||||||
printf("{\"chosen_index\":%d,\"chosen_move\":\"%s\",\"analyze\":{\"candidate_move\":\"%s\","
|
|
||||||
"\"candidate_score\":%.1f}}\n",
|
|
||||||
chosen_idx, chosen, cand, cand_score);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@ -1,750 +0,0 @@
|
|||||||
// Readable micro-Max (https://home.hccnet.nl/h.g.muller/max-src2.html) inspired engine:
|
|
||||||
// CLI-compatible with engine.py Usage:
|
|
||||||
// micro_max_engine [--seed N] [--fen FEN] [--explain] [--analyze UCI] <move1> <move2> ...
|
|
||||||
// Behavior: ranks provided UCI moves using a simple material/king-safety heuristic derived
|
|
||||||
// from the FEN position (if given). Prints chosen move by default, or JSON with --explain.
|
|
||||||
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
char squares[64]; // a1=0 .. h8=63, '.' empty, uppercase white, lowercase black
|
|
||||||
int white_to_move; // 1 white, 0 black
|
|
||||||
} Board;
|
|
||||||
|
|
||||||
static int file_of(int idx) { return idx % 8; }
|
|
||||||
static int rank_of(int idx) { return idx / 8; }
|
|
||||||
static int idx_from_fr(int f, int r) { return (r * 8) + f; }
|
|
||||||
static int on_board(int f, int r) { return f >= 0 && f < 8 && r >= 0 && r < 8; }
|
|
||||||
static int is_white(char p) { return p >= 'A' && p <= 'Z'; }
|
|
||||||
static int is_black(char p) { return p >= 'a' && p <= 'z'; }
|
|
||||||
|
|
||||||
static int piece_value_cp(char p)
|
|
||||||
{
|
|
||||||
switch (tolower((unsigned char)p))
|
|
||||||
{
|
|
||||||
case 'p':
|
|
||||||
return 100;
|
|
||||||
case 'n':
|
|
||||||
return 320;
|
|
||||||
case 'b':
|
|
||||||
return 330;
|
|
||||||
case 'r':
|
|
||||||
return 500;
|
|
||||||
case 'q':
|
|
||||||
return 900;
|
|
||||||
case 'k':
|
|
||||||
return 0;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int parse_fen(Board *b, const char *fen)
|
|
||||||
{
|
|
||||||
memset(b->squares, '.', sizeof(b->squares));
|
|
||||||
b->white_to_move = 1;
|
|
||||||
if (!fen || !*fen)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int f = 0;
|
|
||||||
int r = 7;
|
|
||||||
const char *p = fen;
|
|
||||||
while (*p && *p != ' ')
|
|
||||||
{
|
|
||||||
char c = *p++;
|
|
||||||
if (c == '/')
|
|
||||||
{
|
|
||||||
f = 0;
|
|
||||||
r--;
|
|
||||||
if (r < 0)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (c >= '1' && c <= '8')
|
|
||||||
{
|
|
||||||
f += (c - '0');
|
|
||||||
if (f > 8)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (isalpha((unsigned char)c))
|
|
||||||
{
|
|
||||||
if (f >= 8 || r < 0)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
b->squares[idx_from_fr(f, r)] = c;
|
|
||||||
f++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (*p == ' ')
|
|
||||||
{
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
if (*p == 'w')
|
|
||||||
{
|
|
||||||
b->white_to_move = 1;
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
else if (*p == 'b')
|
|
||||||
{
|
|
||||||
b->white_to_move = 0;
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int find_king(const Board *b, int white)
|
|
||||||
{
|
|
||||||
char k = white ? 'K' : 'k';
|
|
||||||
for (int i = 0; i < 64; ++i)
|
|
||||||
{
|
|
||||||
if (b->squares[i] == k)
|
|
||||||
{
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int sq_attacked_by(const Board *b, int target_idx, int by_white)
|
|
||||||
{
|
|
||||||
int tf = file_of(target_idx);
|
|
||||||
int tr = rank_of(target_idx);
|
|
||||||
// Knights
|
|
||||||
const int kdf[8] = {1, 2, 2, 1, -1, -2, -2, -1};
|
|
||||||
const int kdr[8] = {2, 1, -1, -2, 2, 1, -1, -2};
|
|
||||||
for (int i = 0; i < 8; ++i)
|
|
||||||
{
|
|
||||||
int f = tf + kdf[i];
|
|
||||||
int r = tr + kdr[i];
|
|
||||||
if (on_board(f, r))
|
|
||||||
{
|
|
||||||
char p = b->squares[idx_from_fr(f, r)];
|
|
||||||
if (by_white ? p == 'N' : p == 'n')
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// King
|
|
||||||
for (int df = -1; df <= 1; ++df)
|
|
||||||
{
|
|
||||||
for (int dr = -1; dr <= 1; ++dr)
|
|
||||||
{
|
|
||||||
if (df || dr)
|
|
||||||
{
|
|
||||||
int f = tf + df;
|
|
||||||
int r = tr + dr;
|
|
||||||
if (on_board(f, r))
|
|
||||||
{
|
|
||||||
char p = b->squares[idx_from_fr(f, r)];
|
|
||||||
if (by_white ? p == 'K' : p == 'k')
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Pawns
|
|
||||||
if (by_white)
|
|
||||||
{
|
|
||||||
int f1 = tf - 1;
|
|
||||||
int r1 = tr - 1;
|
|
||||||
int f2 = tf + 1;
|
|
||||||
int r2 = tr - 1;
|
|
||||||
if (on_board(f1, r1) && b->squares[idx_from_fr(f1, r1)] == 'P')
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (on_board(f2, r2) && b->squares[idx_from_fr(f2, r2)] == 'P')
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int f1 = tf - 1;
|
|
||||||
int r1 = tr + 1;
|
|
||||||
int f2 = tf + 1;
|
|
||||||
int r2 = tr + 1;
|
|
||||||
if (on_board(f1, r1) && b->squares[idx_from_fr(f1, r1)] == 'p')
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (on_board(f2, r2) && b->squares[idx_from_fr(f2, r2)] == 'p')
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Bishops/queens
|
|
||||||
const int dsf[4] = {1, 1, -1, -1};
|
|
||||||
const int dsr[4] = {1, -1, 1, -1};
|
|
||||||
for (int d = 0; d < 4; ++d)
|
|
||||||
{
|
|
||||||
int f = tf + dsf[d];
|
|
||||||
int r = tr + dsr[d];
|
|
||||||
while (on_board(f, r))
|
|
||||||
{
|
|
||||||
char p = b->squares[idx_from_fr(f, r)];
|
|
||||||
if (p != '.')
|
|
||||||
{
|
|
||||||
if (by_white ? (p == 'B' || p == 'Q') : (p == 'b' || p == 'q'))
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
f += dsf[d];
|
|
||||||
r += dsr[d];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Rooks/queens
|
|
||||||
const int rsf[4] = {1, -1, 0, 0};
|
|
||||||
const int rsr[4] = {0, 0, 1, -1};
|
|
||||||
for (int d = 0; d < 4; ++d)
|
|
||||||
{
|
|
||||||
int f = tf + rsf[d];
|
|
||||||
int r = tr + rsr[d];
|
|
||||||
while (on_board(f, r))
|
|
||||||
{
|
|
||||||
char p = b->squares[idx_from_fr(f, r)];
|
|
||||||
if (p != '.')
|
|
||||||
{
|
|
||||||
if (by_white ? (p == 'R' || p == 'Q') : (p == 'r' || p == 'q'))
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
f += rsf[d];
|
|
||||||
r += rsr[d];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int count_attackers(const Board *b, int target_idx, int by_white)
|
|
||||||
{
|
|
||||||
int tf = file_of(target_idx);
|
|
||||||
int tr = rank_of(target_idx);
|
|
||||||
int cnt = 0;
|
|
||||||
const int kdf[8] = {1, 2, 2, 1, -1, -2, -2, -1};
|
|
||||||
const int kdr[8] = {2, 1, -1, -2, 2, 1, -1, -2};
|
|
||||||
for (int i = 0; i < 8; ++i)
|
|
||||||
{
|
|
||||||
int f = tf + kdf[i];
|
|
||||||
int r = tr + kdr[i];
|
|
||||||
if (on_board(f, r))
|
|
||||||
{
|
|
||||||
char p = b->squares[idx_from_fr(f, r)];
|
|
||||||
if (by_white ? p == 'N' : p == 'n')
|
|
||||||
{
|
|
||||||
cnt++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int df = -1; df <= 1; ++df)
|
|
||||||
{
|
|
||||||
for (int dr = -1; dr <= 1; ++dr)
|
|
||||||
{
|
|
||||||
if (df || dr)
|
|
||||||
{
|
|
||||||
int f = tf + df;
|
|
||||||
int r = tr + dr;
|
|
||||||
if (on_board(f, r))
|
|
||||||
{
|
|
||||||
char p = b->squares[idx_from_fr(f, r)];
|
|
||||||
if (by_white ? p == 'K' : p == 'k')
|
|
||||||
{
|
|
||||||
cnt++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (by_white)
|
|
||||||
{
|
|
||||||
int f1 = tf - 1;
|
|
||||||
int r1 = tr - 1;
|
|
||||||
int f2 = tf + 1;
|
|
||||||
int r2 = tr - 1;
|
|
||||||
if (on_board(f1, r1) && b->squares[idx_from_fr(f1, r1)] == 'P')
|
|
||||||
{
|
|
||||||
cnt++;
|
|
||||||
}
|
|
||||||
if (on_board(f2, r2) && b->squares[idx_from_fr(f2, r2)] == 'P')
|
|
||||||
{
|
|
||||||
cnt++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int f1 = tf - 1;
|
|
||||||
int r1 = tr + 1;
|
|
||||||
int f2 = tf + 1;
|
|
||||||
int r2 = tr + 1;
|
|
||||||
if (on_board(f1, r1) && b->squares[idx_from_fr(f1, r1)] == 'p')
|
|
||||||
{
|
|
||||||
cnt++;
|
|
||||||
}
|
|
||||||
if (on_board(f2, r2) && b->squares[idx_from_fr(f2, r2)] == 'p')
|
|
||||||
{
|
|
||||||
cnt++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const int dsf[4] = {1, 1, -1, -1};
|
|
||||||
const int dsr[4] = {1, -1, 1, -1};
|
|
||||||
for (int d = 0; d < 4; ++d)
|
|
||||||
{
|
|
||||||
int f = tf + dsf[d];
|
|
||||||
int r = tr + dsr[d];
|
|
||||||
while (on_board(f, r))
|
|
||||||
{
|
|
||||||
char p = b->squares[idx_from_fr(f, r)];
|
|
||||||
if (p != '.')
|
|
||||||
{
|
|
||||||
if (by_white ? (p == 'B' || p == 'Q') : (p == 'b' || p == 'q'))
|
|
||||||
{
|
|
||||||
cnt++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
f += dsf[d];
|
|
||||||
r += dsr[d];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const int rsf[4] = {1, -1, 0, 0};
|
|
||||||
const int rsr[4] = {0, 0, 1, -1};
|
|
||||||
for (int d = 0; d < 4; ++d)
|
|
||||||
{
|
|
||||||
int f = tf + rsf[d];
|
|
||||||
int r = tr + rsr[d];
|
|
||||||
while (on_board(f, r))
|
|
||||||
{
|
|
||||||
char p = b->squares[idx_from_fr(f, r)];
|
|
||||||
if (p != '.')
|
|
||||||
{
|
|
||||||
if (by_white ? (p == 'R' || p == 'Q') : (p == 'r' || p == 'q'))
|
|
||||||
{
|
|
||||||
cnt++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
f += rsf[d];
|
|
||||||
r += rsr[d];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cnt;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int material_cp(const Board *b)
|
|
||||||
{
|
|
||||||
int w = 0;
|
|
||||||
int bl = 0;
|
|
||||||
for (int i = 0; i < 64; ++i)
|
|
||||||
{
|
|
||||||
char p = b->squares[i];
|
|
||||||
if (p == '.')
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int v = piece_value_cp(p);
|
|
||||||
if (is_white(p))
|
|
||||||
{
|
|
||||||
w += v;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bl += v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return w - bl;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int parse_uci(const char *uci, int *from, int *to, char *prom)
|
|
||||||
{
|
|
||||||
if (!uci || strlen(uci) < 4)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int f1 = uci[0] - 'a';
|
|
||||||
int r1 = uci[1] - '1';
|
|
||||||
int f2 = uci[2] - 'a';
|
|
||||||
int r2 = uci[3] - '1';
|
|
||||||
if (!on_board(f1, r1) || !on_board(f2, r2))
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
*from = idx_from_fr(f1, r1);
|
|
||||||
*to = idx_from_fr(f2, r2);
|
|
||||||
*prom = (uci[4] ? uci[4] : 0);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void apply_move(const Board *in, const char *uci, Board *out, int *cap_cp, int *prom_gain_cp)
|
|
||||||
{
|
|
||||||
*out = *in;
|
|
||||||
*cap_cp = 0;
|
|
||||||
*prom_gain_cp = 0;
|
|
||||||
int from;
|
|
||||||
int to;
|
|
||||||
char prom = 0;
|
|
||||||
if (!parse_uci(uci, &from, &to, &prom))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
char mover = out->squares[from];
|
|
||||||
char captured = out->squares[to];
|
|
||||||
if (captured != '.')
|
|
||||||
{
|
|
||||||
*cap_cp = piece_value_cp(captured);
|
|
||||||
}
|
|
||||||
out->squares[to] = mover;
|
|
||||||
out->squares[from] = '.';
|
|
||||||
if (prom)
|
|
||||||
{
|
|
||||||
int is_w = is_white(mover);
|
|
||||||
char p = (char)tolower((unsigned char)prom);
|
|
||||||
char prom_piece = p == 'q' ? (is_w ? 'Q' : 'q')
|
|
||||||
: p == 'r' ? (is_w ? 'R' : 'r')
|
|
||||||
: p == 'b' ? (is_w ? 'B' : 'b')
|
|
||||||
: (is_w ? 'N' : 'n');
|
|
||||||
int gain = piece_value_cp(prom_piece) - piece_value_cp(is_w ? 'P' : 'p');
|
|
||||||
*prom_gain_cp = gain;
|
|
||||||
out->squares[to] = prom_piece;
|
|
||||||
}
|
|
||||||
out->white_to_move = !in->white_to_move;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
char uci[16];
|
|
||||||
double cap_cp;
|
|
||||||
double prom_cp;
|
|
||||||
double mat_cp;
|
|
||||||
double atk_opp_king;
|
|
||||||
double opp_king_mob;
|
|
||||||
double piece_cp;
|
|
||||||
double opp_min_att_cp;
|
|
||||||
double us_min_att_cp;
|
|
||||||
double see_cp;
|
|
||||||
double risk_cp;
|
|
||||||
int gives_check;
|
|
||||||
double score;
|
|
||||||
} MoveInfo;
|
|
||||||
|
|
||||||
static double score_move(const MoveInfo *m, unsigned int seed)
|
|
||||||
{
|
|
||||||
double s = 0.0;
|
|
||||||
if (m->gives_check)
|
|
||||||
{
|
|
||||||
double add = 200.0 + (40.0 * m->atk_opp_king) - (35.0 * m->opp_king_mob);
|
|
||||||
if (m->opp_king_mob <= 0.0)
|
|
||||||
{
|
|
||||||
add += 800.0;
|
|
||||||
}
|
|
||||||
s += add;
|
|
||||||
}
|
|
||||||
s += 1.5 * m->cap_cp;
|
|
||||||
if (m->cap_cp > 0.0)
|
|
||||||
{
|
|
||||||
double exch = m->cap_cp - m->piece_cp;
|
|
||||||
s += (m->piece_cp <= 120.0 ? 1.0 : 3.0) * exch;
|
|
||||||
if (m->piece_cp >= 850.0)
|
|
||||||
{
|
|
||||||
s -= 150.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s += 2.0 * m->prom_cp;
|
|
||||||
s += 1.2 * m->mat_cp;
|
|
||||||
s += 0.2 * m->see_cp;
|
|
||||||
s -= 1.0 * m->risk_cp;
|
|
||||||
double jitter = (double)(seed % 1000) / 1000000.0;
|
|
||||||
return s + jitter;
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned int parse_seed(int *pargc, char ***pargv)
|
|
||||||
{
|
|
||||||
unsigned int seed = (unsigned int)time(NULL) ^ (unsigned int)getpid();
|
|
||||||
int argc = *pargc;
|
|
||||||
char **argv = *pargv;
|
|
||||||
for (int i = 1; i < argc; ++i)
|
|
||||||
{
|
|
||||||
if (strcmp(argv[i], "--seed") == 0 && i + 1 < argc)
|
|
||||||
{
|
|
||||||
seed = (unsigned int)strtoul(argv[i + 1], NULL, 10);
|
|
||||||
for (int j = i; j + 2 < argc; ++j)
|
|
||||||
{
|
|
||||||
argv[j] = argv[j + 2];
|
|
||||||
}
|
|
||||||
*pargc -= 2;
|
|
||||||
return seed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return seed;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
if (argc <= 1)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "usage: %s [--seed N] [--fen FEN] [--explain] [--analyze UCI] <moves...>\n",
|
|
||||||
argv[0]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
unsigned int seed = parse_seed(&argc, &argv);
|
|
||||||
srand(seed);
|
|
||||||
int explain = 0;
|
|
||||||
const char *analyze_uci = NULL;
|
|
||||||
const char *fen = NULL;
|
|
||||||
for (int i = 1; i < argc; ++i)
|
|
||||||
{
|
|
||||||
if (strcmp(argv[i], "--explain") == 0)
|
|
||||||
{
|
|
||||||
explain = 1;
|
|
||||||
for (int j = i; j + 1 < argc; ++j)
|
|
||||||
{
|
|
||||||
argv[j] = argv[j + 1];
|
|
||||||
}
|
|
||||||
argc -= 1;
|
|
||||||
i -= 1;
|
|
||||||
}
|
|
||||||
else if (strcmp(argv[i], "--fen") == 0 && i + 1 < argc)
|
|
||||||
{
|
|
||||||
fen = argv[i + 1];
|
|
||||||
for (int j = i; j + 2 < argc; ++j)
|
|
||||||
{
|
|
||||||
argv[j] = argv[j + 2];
|
|
||||||
}
|
|
||||||
argc -= 2;
|
|
||||||
i -= 1;
|
|
||||||
}
|
|
||||||
else if (strcmp(argv[i], "--analyze") == 0 && i + 1 < argc)
|
|
||||||
{
|
|
||||||
analyze_uci = argv[i + 1];
|
|
||||||
for (int j = i; j + 2 < argc; ++j)
|
|
||||||
{
|
|
||||||
argv[j] = argv[j + 2];
|
|
||||||
}
|
|
||||||
argc -= 2;
|
|
||||||
i -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (argc <= 1)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "no moves provided\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
int n = argc - 1;
|
|
||||||
char **moves = &argv[1];
|
|
||||||
Board board;
|
|
||||||
int have_pos = 0;
|
|
||||||
if (fen)
|
|
||||||
{
|
|
||||||
if (!parse_fen(&board, fen))
|
|
||||||
{
|
|
||||||
fprintf(stderr, "invalid FEN\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
have_pos = 1;
|
|
||||||
}
|
|
||||||
int base_mat = 0;
|
|
||||||
if (have_pos)
|
|
||||||
{
|
|
||||||
base_mat = material_cp(&board);
|
|
||||||
}
|
|
||||||
MoveInfo *arr = (MoveInfo *)calloc((size_t)n, sizeof(MoveInfo));
|
|
||||||
if (!arr)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "alloc failed\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
strncpy(arr[i].uci, moves[i], sizeof(arr[i].uci) - 1);
|
|
||||||
arr[i].uci[sizeof(arr[i].uci) - 1] = '\0';
|
|
||||||
if (!have_pos)
|
|
||||||
{
|
|
||||||
arr[i].score = (double)rand() / (double)RAND_MAX;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Board after = board;
|
|
||||||
int cap = 0;
|
|
||||||
int pg = 0;
|
|
||||||
apply_move(&board, arr[i].uci, &after, &cap, &pg);
|
|
||||||
int mat_after = material_cp(&after);
|
|
||||||
int mat_raw = mat_after - base_mat;
|
|
||||||
int mat_signed = board.white_to_move ? mat_raw : -mat_raw;
|
|
||||||
arr[i].cap_cp = cap;
|
|
||||||
arr[i].prom_cp = pg;
|
|
||||||
arr[i].mat_cp = (double)mat_signed;
|
|
||||||
int from;
|
|
||||||
int to;
|
|
||||||
char pr = 0;
|
|
||||||
if (parse_uci(arr[i].uci, &from, &to, &pr))
|
|
||||||
{
|
|
||||||
char landed = after.squares[to];
|
|
||||||
arr[i].piece_cp = piece_value_cp(landed);
|
|
||||||
int opp_is_white = after.white_to_move;
|
|
||||||
int us_is_white = !after.white_to_move;
|
|
||||||
int opp_min = 0;
|
|
||||||
int us_min = 0;
|
|
||||||
// Use min of attackers by value (crude)
|
|
||||||
// We'll reuse piece values by scanning all pieces; simpler via count_attackers()
|
|
||||||
// surrogate Here we approximate with: if square attacked at all, assume min attacker is
|
|
||||||
// pawn (100)
|
|
||||||
if (sq_attacked_by(&after, to, opp_is_white))
|
|
||||||
{
|
|
||||||
opp_min = 100;
|
|
||||||
}
|
|
||||||
if (sq_attacked_by(&after, to, us_is_white))
|
|
||||||
{
|
|
||||||
us_min = 100;
|
|
||||||
}
|
|
||||||
arr[i].opp_min_att_cp = opp_min;
|
|
||||||
arr[i].us_min_att_cp = us_min;
|
|
||||||
arr[i].see_cp = (cap > 0) ? ((double)cap - (double)opp_min) : 0.0;
|
|
||||||
if (cap == 0 && opp_min > 0 && us_min == 0)
|
|
||||||
{
|
|
||||||
double risk = (double)opp_min;
|
|
||||||
if (risk > arr[i].piece_cp)
|
|
||||||
{
|
|
||||||
risk = arr[i].piece_cp;
|
|
||||||
}
|
|
||||||
arr[i].risk_cp = risk;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int opp_white = !board.white_to_move;
|
|
||||||
int opp_king = find_king(&after, opp_white);
|
|
||||||
int gives = 0;
|
|
||||||
if (opp_king >= 0)
|
|
||||||
{
|
|
||||||
gives = sq_attacked_by(&after, opp_king, !after.white_to_move);
|
|
||||||
int atk = count_attackers(&after, opp_king, !after.white_to_move);
|
|
||||||
int mob = 0;
|
|
||||||
int kf = file_of(opp_king);
|
|
||||||
int kr = rank_of(opp_king);
|
|
||||||
for (int df = -1; df <= 1; ++df)
|
|
||||||
{
|
|
||||||
for (int dr = -1; dr <= 1; ++dr)
|
|
||||||
{
|
|
||||||
if (df || dr)
|
|
||||||
{
|
|
||||||
int f = kf + df;
|
|
||||||
int r = kr + dr;
|
|
||||||
if (!on_board(f, r))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int idx = idx_from_fr(f, r);
|
|
||||||
char occ = after.squares[idx];
|
|
||||||
if (occ != '.' && (opp_white ? is_white(occ) : is_black(occ)))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!sq_attacked_by(&after, idx, !after.white_to_move))
|
|
||||||
{
|
|
||||||
mob++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
arr[i].atk_opp_king = atk;
|
|
||||||
arr[i].opp_king_mob = mob;
|
|
||||||
}
|
|
||||||
arr[i].gives_check = gives;
|
|
||||||
unsigned int local = seed ^ ((unsigned int)i * 2654435761U);
|
|
||||||
arr[i].score = score_move(&arr[i], local);
|
|
||||||
}
|
|
||||||
double best_score = -1e300;
|
|
||||||
int best_idx = -1;
|
|
||||||
for (int i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
if (arr[i].score > best_score)
|
|
||||||
{
|
|
||||||
best_score = arr[i].score;
|
|
||||||
best_idx = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (best_idx < 0)
|
|
||||||
{
|
|
||||||
free(arr);
|
|
||||||
fprintf(stderr, "no moves\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (!explain)
|
|
||||||
{
|
|
||||||
printf("%s\n", arr[best_idx].uci);
|
|
||||||
free(arr);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
printf("{\n");
|
|
||||||
printf(" \"seed\": %u,\n", seed);
|
|
||||||
if (have_pos)
|
|
||||||
{
|
|
||||||
printf(" \"fen\": \"%s\",\n", fen);
|
|
||||||
printf(" \"side_to_move\": \"%s\",\n", board.white_to_move ? "white" : "black");
|
|
||||||
printf(" \"base_material_cp\": %d,\n", base_mat);
|
|
||||||
}
|
|
||||||
printf(" \"n\": %d,\n", n);
|
|
||||||
printf(" \"moves\": [");
|
|
||||||
for (int i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
printf("\"%s\"%s", arr[i].uci, (i + 1 < n ? ", " : ""));
|
|
||||||
}
|
|
||||||
printf("],\n");
|
|
||||||
printf(" \"scores\": [");
|
|
||||||
for (int i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
printf("%.6f%s", arr[i].score, (i + 1 < n ? ", " : ""));
|
|
||||||
}
|
|
||||||
printf("],\n");
|
|
||||||
printf(" \"chosen_index\": %d,\n", best_idx);
|
|
||||||
printf(" \"chosen_move\": \"%s\"", arr[best_idx].uci);
|
|
||||||
if (analyze_uci)
|
|
||||||
{
|
|
||||||
int cand_idx = -1;
|
|
||||||
for (int i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
if (strcmp(arr[i].uci, analyze_uci) == 0)
|
|
||||||
{
|
|
||||||
cand_idx = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
double cand_score = (cand_idx >= 0 ? arr[cand_idx].score : -1.0);
|
|
||||||
const char *cmp = "unknown";
|
|
||||||
if (cand_idx >= 0)
|
|
||||||
{
|
|
||||||
cmp = (cand_score > best_score ? "higher"
|
|
||||||
: (cand_score < best_score ? "lower" : "equal"));
|
|
||||||
}
|
|
||||||
printf(",\n \"analyze\": { \"candidate\": \"%s\", \"candidate_index\": %d, "
|
|
||||||
"\"candidate_score\": %.6f, \"compare_to_chosen\": \"%s\" }\n",
|
|
||||||
analyze_uci, cand_idx, cand_score, cmp);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
printf("}\n");
|
|
||||||
free(arr);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@ -1,948 +0,0 @@
|
|||||||
#include "movegen.h"
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
static inline int on_board(int sq) { return (sq & 0x88) == 0; }
|
|
||||||
static inline int rank_of(int sq) { return sq >> 4; }
|
|
||||||
static inline int file_of(int sq) { return sq & 7; }
|
|
||||||
|
|
||||||
static inline int color_of(Piece p) { return (p >= BP); }
|
|
||||||
static inline int is_white(Piece p) { return p >= WP && p <= WK; }
|
|
||||||
static inline int is_black(Piece p) { return p >= BP && p <= BK; }
|
|
||||||
|
|
||||||
static Piece make_piece(char c)
|
|
||||||
{
|
|
||||||
switch (c)
|
|
||||||
{
|
|
||||||
case 'P':
|
|
||||||
return WP;
|
|
||||||
case 'N':
|
|
||||||
return WN;
|
|
||||||
case 'B':
|
|
||||||
return WB;
|
|
||||||
case 'R':
|
|
||||||
return WR;
|
|
||||||
case 'Q':
|
|
||||||
return WQ;
|
|
||||||
case 'K':
|
|
||||||
return WK;
|
|
||||||
case 'p':
|
|
||||||
return BP;
|
|
||||||
case 'n':
|
|
||||||
return BN;
|
|
||||||
case 'b':
|
|
||||||
return BB;
|
|
||||||
case 'r':
|
|
||||||
return BR;
|
|
||||||
case 'q':
|
|
||||||
return BQ;
|
|
||||||
case 'k':
|
|
||||||
return BK;
|
|
||||||
default:
|
|
||||||
return EMPTY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_startpos(Position *pos)
|
|
||||||
{
|
|
||||||
memset(pos, 0, sizeof(*pos));
|
|
||||||
for (int i = 0; i < BOARD_SIZE; i++)
|
|
||||||
{
|
|
||||||
pos->board[i] = EMPTY;
|
|
||||||
}
|
|
||||||
const char *start = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
|
|
||||||
char fen[128];
|
|
||||||
strcpy(fen, start);
|
|
||||||
strcat(fen, " w KQkq - 0 1");
|
|
||||||
parse_fen(pos, fen);
|
|
||||||
}
|
|
||||||
|
|
||||||
int parse_fen(Position *pos, const char *fen)
|
|
||||||
{
|
|
||||||
memset(pos, 0, sizeof(*pos));
|
|
||||||
for (int i = 0; i < BOARD_SIZE; i++)
|
|
||||||
{
|
|
||||||
pos->board[i] = EMPTY;
|
|
||||||
}
|
|
||||||
pos->ep_square = -1;
|
|
||||||
pos->castle = 0;
|
|
||||||
pos->halfmove_clock = 0;
|
|
||||||
pos->fullmove_number = 1;
|
|
||||||
|
|
||||||
// pieces
|
|
||||||
int sq = 0x70; // A8
|
|
||||||
const char *p = fen;
|
|
||||||
while (*p && *p != ' ')
|
|
||||||
{
|
|
||||||
if (*p == '/')
|
|
||||||
{
|
|
||||||
sq = (sq & 0x70) - 0x10;
|
|
||||||
p++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (isdigit((unsigned char)*p))
|
|
||||||
{
|
|
||||||
sq += (*p - '0');
|
|
||||||
p++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Piece pc = make_piece(*p++);
|
|
||||||
if (!on_board(sq))
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
pos->board[sq++] = pc;
|
|
||||||
}
|
|
||||||
if (*p != ' ')
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
p++;
|
|
||||||
|
|
||||||
// side
|
|
||||||
if (*p == 'w')
|
|
||||||
{
|
|
||||||
pos->side = WHITE;
|
|
||||||
}
|
|
||||||
else if (*p == 'b')
|
|
||||||
{
|
|
||||||
pos->side = BLACK;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
p++;
|
|
||||||
if (*p != ' ')
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
p++;
|
|
||||||
|
|
||||||
// castling
|
|
||||||
if (*p == '-')
|
|
||||||
{
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
while (*p && *p != ' ')
|
|
||||||
{
|
|
||||||
if (*p == 'K')
|
|
||||||
{
|
|
||||||
pos->castle |= 1 << 0;
|
|
||||||
}
|
|
||||||
else if (*p == 'Q')
|
|
||||||
{
|
|
||||||
pos->castle |= 1 << 1;
|
|
||||||
}
|
|
||||||
else if (*p == 'k')
|
|
||||||
{
|
|
||||||
pos->castle |= 1 << 2;
|
|
||||||
}
|
|
||||||
else if (*p == 'q')
|
|
||||||
{
|
|
||||||
pos->castle |= 1 << 3;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (*p != ' ')
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
p++;
|
|
||||||
|
|
||||||
// en-passant
|
|
||||||
if (*p == '-')
|
|
||||||
{
|
|
||||||
pos->ep_square = -1;
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (p[0] >= 'a' && p[0] <= 'h' && p[1] >= '1' && p[1] <= '8')
|
|
||||||
{
|
|
||||||
int f = p[0] - 'a';
|
|
||||||
int r = p[1] - '1';
|
|
||||||
pos->ep_square = (r << 4) | f;
|
|
||||||
p += 2;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (*p == ' ')
|
|
||||||
{
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// halfmove clock
|
|
||||||
if (isdigit((unsigned char)*p))
|
|
||||||
{
|
|
||||||
pos->halfmove_clock = strtol(p, (char **)&p, 10);
|
|
||||||
}
|
|
||||||
if (*p == ' ')
|
|
||||||
{
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fullmove number
|
|
||||||
if (isdigit((unsigned char)*p))
|
|
||||||
{
|
|
||||||
pos->fullmove_number = strtol(p, NULL, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int add_move(Move *moves, int count, int max, int from, int to, int cap, int promo, int ep,
|
|
||||||
int castle)
|
|
||||||
{
|
|
||||||
if (count >= max)
|
|
||||||
{
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
Move m;
|
|
||||||
m.from = (uint8_t)from;
|
|
||||||
m.to = (uint8_t)to;
|
|
||||||
m.promo = (uint8_t)promo;
|
|
||||||
m.is_capture = (uint8_t)cap;
|
|
||||||
m.is_enpassant = (uint8_t)ep;
|
|
||||||
m.is_castle = (uint8_t)castle;
|
|
||||||
moves[count++] = m;
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check detection via attack lookup
|
|
||||||
static int square_attacked_by(const Position *pos, int sq, Color by)
|
|
||||||
{
|
|
||||||
// Knights
|
|
||||||
static const int kn[] = {33, 31, 18, 14, -33, -31, -18, -14};
|
|
||||||
for (int i = 0; i < 8; i++)
|
|
||||||
{
|
|
||||||
int s = sq + kn[i];
|
|
||||||
if (!on_board(s))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Piece p = pos->board[s];
|
|
||||||
if (by == WHITE && p == WN)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (by == BLACK && p == BN)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Kings
|
|
||||||
static const int kd[] = {1, -1, 16, -16, 17, 15, -17, -15};
|
|
||||||
for (int i = 0; i < 8; i++)
|
|
||||||
{
|
|
||||||
int s = sq + kd[i];
|
|
||||||
if (!on_board(s))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Piece p = pos->board[s];
|
|
||||||
if (by == WHITE && p == WK)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (by == BLACK && p == BK)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Pawns
|
|
||||||
if (by == WHITE)
|
|
||||||
{
|
|
||||||
int s1 = sq - 15;
|
|
||||||
int s2 = sq - 17; // white pawns attack up-left/up-right from their perspective
|
|
||||||
if (on_board(s1) && pos->board[s1] == WP)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (on_board(s2) && pos->board[s2] == WP)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int s1 = sq + 15;
|
|
||||||
int s2 = sq + 17;
|
|
||||||
if (on_board(s1) && pos->board[s1] == BP)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (on_board(s2) && pos->board[s2] == BP)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Sliders: bishops/queens diagonals
|
|
||||||
static const int bd[] = {17, 15, -17, -15};
|
|
||||||
for (int d = 0; d < 4; ++d)
|
|
||||||
{
|
|
||||||
int s = sq + bd[d];
|
|
||||||
while (on_board(s))
|
|
||||||
{
|
|
||||||
Piece p = pos->board[s];
|
|
||||||
if (p != EMPTY)
|
|
||||||
{
|
|
||||||
if (by == WHITE && (p == WB || p == WQ))
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (by == BLACK && (p == BB || p == BQ))
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
s += bd[d];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Rooks/queens
|
|
||||||
static const int rd[] = {1, -1, 16, -16};
|
|
||||||
for (int d = 0; d < 4; ++d)
|
|
||||||
{
|
|
||||||
int s = sq + rd[d];
|
|
||||||
while (on_board(s))
|
|
||||||
{
|
|
||||||
Piece p = pos->board[s];
|
|
||||||
if (p != EMPTY)
|
|
||||||
{
|
|
||||||
if (by == WHITE && (p == WR || p == WQ))
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (by == BLACK && (p == BR || p == BQ))
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
s += rd[d];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int in_check(const Position *pos, Color side)
|
|
||||||
{
|
|
||||||
// find king square
|
|
||||||
Piece k = (side == WHITE) ? WK : BK;
|
|
||||||
int ks = -1;
|
|
||||||
for (int sq = 0; sq < BOARD_SIZE; ++sq)
|
|
||||||
{
|
|
||||||
if (!on_board(sq))
|
|
||||||
{
|
|
||||||
sq = (sq | 7);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (pos->board[sq] == k)
|
|
||||||
{
|
|
||||||
ks = sq;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ks < 0)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return square_attacked_by(pos, ks, (side == WHITE) ? BLACK : WHITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int gen_moves_internal(const Position *pos, Move *moves, int max_moves, int captures_only)
|
|
||||||
{
|
|
||||||
int count = 0;
|
|
||||||
Color us = pos->side;
|
|
||||||
int forward = (us == WHITE) ? 16 : -16;
|
|
||||||
int start_rank = (us == WHITE) ? 1 : 6;
|
|
||||||
int promo_rank = (us == WHITE) ? 6 : 1; // rank before promotion move (from rank)
|
|
||||||
|
|
||||||
for (int sq = 0; sq < BOARD_SIZE; ++sq)
|
|
||||||
{
|
|
||||||
if (!on_board(sq))
|
|
||||||
{
|
|
||||||
sq = (sq | 7);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Piece p = pos->board[sq];
|
|
||||||
if (p == EMPTY)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ((us == WHITE && !is_white(p)) || (us == BLACK && !is_black(p)))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (p)
|
|
||||||
{
|
|
||||||
case WP:
|
|
||||||
case BP:
|
|
||||||
{
|
|
||||||
int dir = (p == WP) ? 16 : -16;
|
|
||||||
int r = rank_of(sq);
|
|
||||||
// quiet pushes
|
|
||||||
if (!captures_only)
|
|
||||||
{
|
|
||||||
int to = sq + dir;
|
|
||||||
if (on_board(to) && pos->board[to] == EMPTY)
|
|
||||||
{
|
|
||||||
if (r == promo_rank)
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 0,
|
|
||||||
(us == WHITE ? WQ : BQ), 0, 0);
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 0,
|
|
||||||
(us == WHITE ? WR : BR), 0, 0);
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 0,
|
|
||||||
(us == WHITE ? WB : BB), 0, 0);
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 0,
|
|
||||||
(us == WHITE ? WN : BN), 0, 0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 0, 0, 0, 0);
|
|
||||||
// double push from start rank
|
|
||||||
if (r == start_rank)
|
|
||||||
{
|
|
||||||
int to2 = to + dir;
|
|
||||||
if (on_board(to2) && pos->board[to2] == EMPTY)
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, to2, 0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// captures
|
|
||||||
int caps[2] = {sq + dir + 1, sq + dir - 1};
|
|
||||||
for (int i = 0; i < 2; i++)
|
|
||||||
{
|
|
||||||
int to = caps[i];
|
|
||||||
if (!on_board(to))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Piece tp = pos->board[to];
|
|
||||||
if (tp != EMPTY && color_of(tp) != us)
|
|
||||||
{
|
|
||||||
if (r == promo_rank)
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 1,
|
|
||||||
(us == WHITE ? WQ : BQ), 0, 0);
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 1,
|
|
||||||
(us == WHITE ? WR : BR), 0, 0);
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 1,
|
|
||||||
(us == WHITE ? WB : BB), 0, 0);
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 1,
|
|
||||||
(us == WHITE ? WN : BN), 0, 0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 1, 0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// en-passant
|
|
||||||
if (pos->ep_square >= 0)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 2; i++)
|
|
||||||
{
|
|
||||||
int to = caps[i];
|
|
||||||
if (!on_board(to))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (to == pos->ep_square)
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 1, 0, 1, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case WN:
|
|
||||||
case BN:
|
|
||||||
{
|
|
||||||
static const int d[8] = {33, 31, 18, 14, -33, -31, -18, -14};
|
|
||||||
for (int i = 0; i < 8; i++)
|
|
||||||
{
|
|
||||||
int to = sq + d[i];
|
|
||||||
if (!on_board(to))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Piece tp = pos->board[to];
|
|
||||||
if (tp == EMPTY)
|
|
||||||
{
|
|
||||||
if (!captures_only)
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (color_of(tp) != us)
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 1, 0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case WB:
|
|
||||||
case BB:
|
|
||||||
case WR:
|
|
||||||
case BR:
|
|
||||||
case WQ:
|
|
||||||
case BQ:
|
|
||||||
{
|
|
||||||
static const int bd[4] = {17, 15, -17, -15};
|
|
||||||
static const int rd[4] = {1, -1, 16, -16};
|
|
||||||
const int *dirs = NULL;
|
|
||||||
int ndirs = 0;
|
|
||||||
if (p == WB || p == BB)
|
|
||||||
{
|
|
||||||
dirs = bd;
|
|
||||||
ndirs = 4;
|
|
||||||
}
|
|
||||||
else if (p == WR || p == BR)
|
|
||||||
{
|
|
||||||
dirs = rd;
|
|
||||||
ndirs = 4;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{ // queen
|
|
||||||
// iterate both sets
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
int to = sq + bd[i];
|
|
||||||
while (on_board(to))
|
|
||||||
{
|
|
||||||
Piece tp = pos->board[to];
|
|
||||||
if (tp == EMPTY)
|
|
||||||
{
|
|
||||||
if (!captures_only)
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (color_of(tp) != us)
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 1, 0, 0, 0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
to += bd[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
int to = sq + rd[i];
|
|
||||||
while (on_board(to))
|
|
||||||
{
|
|
||||||
Piece tp = pos->board[to];
|
|
||||||
if (tp == EMPTY)
|
|
||||||
{
|
|
||||||
if (!captures_only)
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (color_of(tp) != us)
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 1, 0, 0, 0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
to += rd[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < ndirs; i++)
|
|
||||||
{
|
|
||||||
int to = sq + dirs[i];
|
|
||||||
while (on_board(to))
|
|
||||||
{
|
|
||||||
Piece tp = pos->board[to];
|
|
||||||
if (tp == EMPTY)
|
|
||||||
{
|
|
||||||
if (!captures_only)
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (color_of(tp) != us)
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 1, 0, 0, 0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
to += dirs[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case WK:
|
|
||||||
case BK:
|
|
||||||
{
|
|
||||||
static const int kd[8] = {1, -1, 16, -16, 17, 15, -17, -15};
|
|
||||||
for (int i = 0; i < 8; i++)
|
|
||||||
{
|
|
||||||
int to = sq + kd[i];
|
|
||||||
if (!on_board(to))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Piece tp = pos->board[to];
|
|
||||||
if (tp == EMPTY)
|
|
||||||
{
|
|
||||||
if (!captures_only)
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (color_of(tp) != us)
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, to, 1, 0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// castling (very basic, no check-through validation here; filter later)
|
|
||||||
if (!captures_only)
|
|
||||||
{
|
|
||||||
// Only if not currently in check and path squares are not attacked
|
|
||||||
Color them = (us == WHITE) ? BLACK : WHITE;
|
|
||||||
if (us == WHITE)
|
|
||||||
{
|
|
||||||
if ((pos->castle & (1 << 0)) && pos->board[0x04] == WK &&
|
|
||||||
pos->board[0x05] == EMPTY && pos->board[0x06] == EMPTY)
|
|
||||||
{
|
|
||||||
if (!in_check(pos, WHITE) && !square_attacked_by(pos, 0x05, them) &&
|
|
||||||
!square_attacked_by(pos, 0x06, them))
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, 0x06, 0, 0, 0, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((pos->castle & (1 << 1)) && pos->board[0x03] == EMPTY &&
|
|
||||||
pos->board[0x02] == EMPTY && pos->board[0x01] == EMPTY)
|
|
||||||
{
|
|
||||||
if (!in_check(pos, WHITE) && !square_attacked_by(pos, 0x03, them) &&
|
|
||||||
!square_attacked_by(pos, 0x02, them))
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, 0x02, 0, 0, 0, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if ((pos->castle & (1 << 2)) && pos->board[0x74] == BK &&
|
|
||||||
pos->board[0x75] == EMPTY && pos->board[0x76] == EMPTY)
|
|
||||||
{
|
|
||||||
if (!in_check(pos, BLACK) && !square_attacked_by(pos, 0x75, them) &&
|
|
||||||
!square_attacked_by(pos, 0x76, them))
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, 0x76, 0, 0, 0, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((pos->castle & (1 << 3)) && pos->board[0x73] == EMPTY &&
|
|
||||||
pos->board[0x72] == EMPTY && pos->board[0x71] == EMPTY)
|
|
||||||
{
|
|
||||||
if (!in_check(pos, BLACK) && !square_attacked_by(pos, 0x73, them) &&
|
|
||||||
!square_attacked_by(pos, 0x72, them))
|
|
||||||
{
|
|
||||||
count = add_move(moves, count, max_moves, sq, 0x72, 0, 0, 0, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
int gen_moves_pseudo(const Position *pos, Move *moves, int max_moves, int captures_only)
|
|
||||||
{
|
|
||||||
return gen_moves_internal(pos, moves, max_moves, captures_only);
|
|
||||||
}
|
|
||||||
|
|
||||||
int gen_moves(const Position *pos, Move *moves, int max_moves, int captures_only)
|
|
||||||
{
|
|
||||||
int count = gen_moves_internal(pos, moves, max_moves, captures_only);
|
|
||||||
// Filter illegal moves leaving our king in check
|
|
||||||
for (int i = 0; i < count;)
|
|
||||||
{
|
|
||||||
Position tmp = *pos;
|
|
||||||
Piece cap = EMPTY;
|
|
||||||
make_move(&tmp, &moves[i], &cap);
|
|
||||||
int illegal = in_check(&tmp, pos->side);
|
|
||||||
if (illegal)
|
|
||||||
{
|
|
||||||
moves[i] = moves[count - 1];
|
|
||||||
count--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
void make_move(Position *pos, const Move *m, Piece *captured_out)
|
|
||||||
{
|
|
||||||
Piece fromP = pos->board[m->from];
|
|
||||||
Piece toP = pos->board[m->to];
|
|
||||||
*captured_out = toP;
|
|
||||||
|
|
||||||
// en-passant capture
|
|
||||||
if (m->is_enpassant)
|
|
||||||
{
|
|
||||||
int cap_sq = (pos->side == WHITE) ? (m->to - 16) : (m->to + 16);
|
|
||||||
*captured_out = pos->board[cap_sq];
|
|
||||||
pos->board[cap_sq] = EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
// move piece
|
|
||||||
pos->board[m->to] = fromP;
|
|
||||||
pos->board[m->from] = EMPTY;
|
|
||||||
|
|
||||||
// promotion
|
|
||||||
if (m->promo)
|
|
||||||
{
|
|
||||||
pos->board[m->to] = (Piece)m->promo;
|
|
||||||
}
|
|
||||||
|
|
||||||
// castling rook move
|
|
||||||
if (m->is_castle)
|
|
||||||
{
|
|
||||||
if (fromP == WK && m->to == 0x06)
|
|
||||||
{
|
|
||||||
pos->board[0x05] = WR;
|
|
||||||
pos->board[0x07] = EMPTY;
|
|
||||||
}
|
|
||||||
else if (fromP == WK && m->to == 0x02)
|
|
||||||
{
|
|
||||||
pos->board[0x03] = WR;
|
|
||||||
pos->board[0x00] = EMPTY;
|
|
||||||
}
|
|
||||||
else if (fromP == BK && m->to == 0x76)
|
|
||||||
{
|
|
||||||
pos->board[0x75] = BR;
|
|
||||||
pos->board[0x77] = EMPTY;
|
|
||||||
}
|
|
||||||
else if (fromP == BK && m->to == 0x72)
|
|
||||||
{
|
|
||||||
pos->board[0x73] = BR;
|
|
||||||
pos->board[0x70] = EMPTY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update castling rights conservatively
|
|
||||||
if (fromP == WK)
|
|
||||||
{
|
|
||||||
pos->castle &= ~(1 << 0);
|
|
||||||
pos->castle &= ~(1 << 1);
|
|
||||||
}
|
|
||||||
if (fromP == BK)
|
|
||||||
{
|
|
||||||
pos->castle &= ~(1 << 2);
|
|
||||||
pos->castle &= ~(1 << 3);
|
|
||||||
}
|
|
||||||
if (m->from == 0x00 || m->to == 0x00)
|
|
||||||
{
|
|
||||||
pos->castle &= ~(1 << 1);
|
|
||||||
}
|
|
||||||
if (m->from == 0x07 || m->to == 0x07)
|
|
||||||
{
|
|
||||||
pos->castle &= ~(1 << 0);
|
|
||||||
}
|
|
||||||
if (m->from == 0x70 || m->to == 0x70)
|
|
||||||
{
|
|
||||||
pos->castle &= ~(1 << 3);
|
|
||||||
}
|
|
||||||
if (m->from == 0x77 || m->to == 0x77)
|
|
||||||
{
|
|
||||||
pos->castle &= ~(1 << 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// en-passant square
|
|
||||||
pos->ep_square = -1;
|
|
||||||
if (fromP == WP && (m->to - m->from) == 32)
|
|
||||||
{
|
|
||||||
pos->ep_square = m->from + 16;
|
|
||||||
}
|
|
||||||
if (fromP == BP && (m->from - m->to) == 32)
|
|
||||||
{
|
|
||||||
pos->ep_square = m->from - 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
// halfmove clock
|
|
||||||
if (fromP == WP || fromP == BP || m->is_capture)
|
|
||||||
{
|
|
||||||
pos->halfmove_clock = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pos->halfmove_clock++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// side to move
|
|
||||||
pos->side = (pos->side == WHITE) ? BLACK : WHITE;
|
|
||||||
if (pos->side == WHITE)
|
|
||||||
{
|
|
||||||
pos->fullmove_number++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void unmake_move(Position *pos, const Move *m, Piece captured)
|
|
||||||
{
|
|
||||||
pos->side = (pos->side == WHITE) ? BLACK : WHITE;
|
|
||||||
if (pos->side == BLACK)
|
|
||||||
{
|
|
||||||
pos->fullmove_number--;
|
|
||||||
}
|
|
||||||
|
|
||||||
Piece moved = pos->board[m->to];
|
|
||||||
|
|
||||||
// undo castling rook move
|
|
||||||
if (m->is_castle)
|
|
||||||
{
|
|
||||||
if (moved == WK && m->to == 0x06)
|
|
||||||
{
|
|
||||||
pos->board[0x07] = WR;
|
|
||||||
pos->board[0x05] = EMPTY;
|
|
||||||
}
|
|
||||||
else if (moved == WK && m->to == 0x02)
|
|
||||||
{
|
|
||||||
pos->board[0x00] = WR;
|
|
||||||
pos->board[0x03] = EMPTY;
|
|
||||||
}
|
|
||||||
else if (moved == BK && m->to == 0x76)
|
|
||||||
{
|
|
||||||
pos->board[0x77] = BR;
|
|
||||||
pos->board[0x75] = EMPTY;
|
|
||||||
}
|
|
||||||
else if (moved == BK && m->to == 0x72)
|
|
||||||
{
|
|
||||||
pos->board[0x70] = BR;
|
|
||||||
pos->board[0x73] = EMPTY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// undo promotion
|
|
||||||
if (m->promo)
|
|
||||||
{
|
|
||||||
moved = (pos->side == WHITE) ? WP : BP;
|
|
||||||
}
|
|
||||||
|
|
||||||
pos->board[m->from] = moved;
|
|
||||||
if (m->is_enpassant)
|
|
||||||
{
|
|
||||||
pos->board[m->to] = EMPTY;
|
|
||||||
int cap_sq = (pos->side == WHITE) ? (m->to - 16) : (m->to + 16);
|
|
||||||
pos->board[cap_sq] = captured;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pos->board[m->to] = captured;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: We do not restore previous castle/ep/halfmove here (for perft driver we will handle
|
|
||||||
// state by copying Position before make_move) For correctness in deeper engine, we’d need a
|
|
||||||
// move stack with state; perft here uses position copies for make/unmake. To keep unmake
|
|
||||||
// consistent for our usage (make->unmake on a copy), we keep simple.
|
|
||||||
}
|
|
||||||
|
|
||||||
int square_from_algebraic(const char *uci4, int is_from)
|
|
||||||
{
|
|
||||||
// uci like e2e4 or e7e8q
|
|
||||||
if (!uci4 || strlen(uci4) < 4)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int f = uci4[is_from ? 0 : 2] - 'a';
|
|
||||||
int r = uci4[is_from ? 1 : 3] - '1';
|
|
||||||
if (f < 0 || f > 7 || r < 0 || r > 7)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return (r << 4) | f;
|
|
||||||
}
|
|
||||||
|
|
||||||
int move_from_uci(const Position *pos, const char *uci, Move *out)
|
|
||||||
{
|
|
||||||
int from = square_from_algebraic(uci, 1);
|
|
||||||
int to = square_from_algebraic(uci, 0);
|
|
||||||
if (from < 0 || to < 0)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
char promo = 0;
|
|
||||||
if (strlen(uci) >= 5)
|
|
||||||
{
|
|
||||||
promo = uci[4];
|
|
||||||
}
|
|
||||||
Move moves[256];
|
|
||||||
int n = gen_moves(pos, moves, 256, 0);
|
|
||||||
for (int i = 0; i < n; i++)
|
|
||||||
{
|
|
||||||
if (moves[i].from == from && moves[i].to == to)
|
|
||||||
{
|
|
||||||
if (moves[i].promo)
|
|
||||||
{
|
|
||||||
// map promo char
|
|
||||||
Piece pp = 0;
|
|
||||||
if (promo == 'q' || promo == 'Q')
|
|
||||||
{
|
|
||||||
pp = (pos->side == WHITE) ? WQ : BQ;
|
|
||||||
}
|
|
||||||
else if (promo == 'r' || promo == 'R')
|
|
||||||
{
|
|
||||||
pp = (pos->side == WHITE) ? WR : BR;
|
|
||||||
}
|
|
||||||
else if (promo == 'b' || promo == 'B')
|
|
||||||
{
|
|
||||||
pp = (pos->side == WHITE) ? WB : BB;
|
|
||||||
}
|
|
||||||
else if (promo == 'n' || promo == 'N')
|
|
||||||
{
|
|
||||||
pp = (pos->side == WHITE) ? WN : BN;
|
|
||||||
}
|
|
||||||
if (pp && pp == moves[i].promo)
|
|
||||||
{
|
|
||||||
*out = moves[i];
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!promo)
|
|
||||||
{
|
|
||||||
*out = moves[i];
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
#ifndef MOVEGEN_H
|
|
||||||
#define MOVEGEN_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
// 0x88 board representation
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
BOARD_SIZE = 128
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef enum
|
|
||||||
{
|
|
||||||
WHITE = 0,
|
|
||||||
BLACK = 1
|
|
||||||
} Color;
|
|
||||||
|
|
||||||
typedef enum
|
|
||||||
{
|
|
||||||
EMPTY = 0,
|
|
||||||
WP = 1,
|
|
||||||
WN = 2,
|
|
||||||
WB = 3,
|
|
||||||
WR = 4,
|
|
||||||
WQ = 5,
|
|
||||||
WK = 6,
|
|
||||||
BP = 7,
|
|
||||||
BN = 8,
|
|
||||||
BB = 9,
|
|
||||||
BR = 10,
|
|
||||||
BQ = 11,
|
|
||||||
BK = 12
|
|
||||||
} Piece;
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
// from and to squares in 0x88 (0..127), promotion piece in Piece enum or 0
|
|
||||||
uint8_t from, to;
|
|
||||||
uint8_t promo; // 0 if none
|
|
||||||
uint8_t is_capture; // 1 if capture
|
|
||||||
uint8_t is_enpassant; // 1 if en-passant capture
|
|
||||||
uint8_t is_castle; // 1 if castle
|
|
||||||
} Move;
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
Piece board[BOARD_SIZE];
|
|
||||||
Color side;
|
|
||||||
// Castling rights: bit 0 white king-side, 1 white queen-side, 2 black king-side, 3 black
|
|
||||||
// queen-side
|
|
||||||
uint8_t castle;
|
|
||||||
int8_t ep_square; // -1 if none, else 0x88 square index
|
|
||||||
int halfmove_clock;
|
|
||||||
int fullmove_number;
|
|
||||||
} Position;
|
|
||||||
|
|
||||||
// Parsing and utilities
|
|
||||||
int parse_fen(Position *pos, const char *fen);
|
|
||||||
void set_startpos(Position *pos);
|
|
||||||
int square_from_algebraic(const char *uci4, int is_from);
|
|
||||||
int move_from_uci(const Position *pos, const char *uci, Move *out);
|
|
||||||
void make_move(Position *pos, const Move *m, Piece *captured_out);
|
|
||||||
void unmake_move(Position *pos, const Move *m, Piece captured);
|
|
||||||
int in_check(const Position *pos, Color side);
|
|
||||||
|
|
||||||
// Move generation
|
|
||||||
// Generates all pseudo-legal moves into moves[], returns count. If captures_only!=0, only captures
|
|
||||||
// (incl. ep) are generated
|
|
||||||
int gen_moves(const Position *pos, Move *moves, int max_moves, int captures_only);
|
|
||||||
int gen_moves_pseudo(const Position *pos, Move *moves, int max_moves, int captures_only);
|
|
||||||
|
|
||||||
#endif // MOVEGEN_H
|
|
||||||
@ -1,146 +0,0 @@
|
|||||||
#include "movegen.h"
|
|
||||||
#include <limits.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
static unsigned long long perft(Position pos, int depth)
|
|
||||||
{
|
|
||||||
if (depth == 0)
|
|
||||||
{
|
|
||||||
return 1ULL;
|
|
||||||
}
|
|
||||||
Move moves[256];
|
|
||||||
unsigned long long nodes = 0ULL;
|
|
||||||
int n = gen_moves(&pos, moves, 256, 0);
|
|
||||||
for (int i = 0; i < n; i++)
|
|
||||||
{
|
|
||||||
Position child = pos;
|
|
||||||
Piece cap = EMPTY;
|
|
||||||
make_move(&child, &moves[i], &cap);
|
|
||||||
nodes += perft(child, depth - 1);
|
|
||||||
}
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void uci_from_move(const Move *m, char *buf)
|
|
||||||
{
|
|
||||||
int ff = (m->from & 7);
|
|
||||||
int fr = (m->from >> 4);
|
|
||||||
int tf = (m->to & 7);
|
|
||||||
int tr = (m->to >> 4);
|
|
||||||
buf[0] = (char)('a' + ff);
|
|
||||||
buf[1] = (char)('1' + fr);
|
|
||||||
buf[2] = (char)('a' + tf);
|
|
||||||
buf[3] = (char)('1' + tr);
|
|
||||||
int i = 4;
|
|
||||||
if (m->promo)
|
|
||||||
{
|
|
||||||
char pc = 'q';
|
|
||||||
switch (m->promo)
|
|
||||||
{
|
|
||||||
case WQ:
|
|
||||||
case BQ:
|
|
||||||
pc = 'q';
|
|
||||||
break;
|
|
||||||
case WR:
|
|
||||||
case BR:
|
|
||||||
pc = 'r';
|
|
||||||
break;
|
|
||||||
case WB:
|
|
||||||
case BB:
|
|
||||||
pc = 'b';
|
|
||||||
break;
|
|
||||||
case WN:
|
|
||||||
case BN:
|
|
||||||
pc = 'n';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
pc = 'q';
|
|
||||||
}
|
|
||||||
buf[i++] = pc;
|
|
||||||
}
|
|
||||||
buf[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void run_case(const char *fen, int depth, unsigned long long expected)
|
|
||||||
{
|
|
||||||
Position p;
|
|
||||||
if (!parse_fen(&p, fen))
|
|
||||||
{
|
|
||||||
(void)fprintf(stderr, "Bad FEN: %s\n", fen);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
unsigned long long n = perft(p, depth);
|
|
||||||
printf("perft(%d) = %llu %s\n", depth, n,
|
|
||||||
(expected ? (n == expected ? "OK" : "MISMATCH") : ""));
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
if (argc >= 3)
|
|
||||||
{
|
|
||||||
const char *fen = argv[1];
|
|
||||||
char *depth_end = NULL;
|
|
||||||
long depth_long = strtol(argv[2], &depth_end, 10);
|
|
||||||
if (depth_end == argv[2] || *depth_end != '\0' || depth_long < 0 || depth_long > INT_MAX)
|
|
||||||
{
|
|
||||||
(void)fprintf(stderr, "Invalid depth value: %s\n", argv[2]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
int depth = (int)depth_long;
|
|
||||||
Position p;
|
|
||||||
if (!parse_fen(&p, fen))
|
|
||||||
{
|
|
||||||
(void)fprintf(stderr, "Bad FEN input\n");
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
if (argc >= 4 && strcmp(argv[3], "--divide") == 0)
|
|
||||||
{
|
|
||||||
Move moves[256];
|
|
||||||
int n = gen_moves(&p, moves, 256, 0);
|
|
||||||
unsigned long long total = 0ULL;
|
|
||||||
for (int i = 0; i < n; i++)
|
|
||||||
{
|
|
||||||
Position c = p;
|
|
||||||
Piece cap = EMPTY;
|
|
||||||
make_move(&c, &moves[i], &cap);
|
|
||||||
unsigned long long sub = perft(c, depth - 1);
|
|
||||||
char u[8];
|
|
||||||
uci_from_move(&moves[i], u);
|
|
||||||
printf("%s: %llu\n", u, sub);
|
|
||||||
total += sub;
|
|
||||||
}
|
|
||||||
printf("Total: %llu\n", total);
|
|
||||||
}
|
|
||||||
else if (argc >= 4 && strcmp(argv[3], "--divide-pseudo") == 0)
|
|
||||||
{
|
|
||||||
Move moves[256];
|
|
||||||
int n = gen_moves_pseudo(&p, moves, 256, 0);
|
|
||||||
for (int i = 0; i < n; i++)
|
|
||||||
{
|
|
||||||
char u[8];
|
|
||||||
uci_from_move(&moves[i], u);
|
|
||||||
printf("%s\n", u);
|
|
||||||
}
|
|
||||||
printf("Total pseudo: %d\n", n);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
unsigned long long n = perft(p, depth);
|
|
||||||
printf("%llu\n", n);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some well-known positions (depth limited to be fast). Expected nodes are standard perft
|
|
||||||
// values. Start position
|
|
||||||
run_case("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", 1, 20ULL);
|
|
||||||
run_case("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", 2, 400ULL);
|
|
||||||
// Kiwipete
|
|
||||||
run_case("r3k2r/p1ppqpb1/bn2pnp1/2PpP3/1p2P3/2N2N2/PBPP1PPP/R2Q1RK1 w kq - 0 1", 1, 48ULL);
|
|
||||||
run_case("r3k2r/p1ppqpb1/bn2pnp1/2PpP3/1p2P3/2N2N2/PBPP1PPP/R2Q1RK1 w kq - 0 1", 2, 2039ULL);
|
|
||||||
// Simple EP
|
|
||||||
run_case("rnbqkbnr/pppppppp/8/8/3Pp3/8/PPP1PPPP/RNBQKBNR b KQkq d3 0 1", 1, 29ULL);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
make
|
|
||||||
echo "Usage: ./random_engine --fen \"<FEN>\" move1 move2 ..."
|
|
||||||
echo " ./random_engine --fen \"<FEN>\" --explain move1 ..."
|
|
||||||
@ -1,106 +0,0 @@
|
|||||||
#include "search.h"
|
|
||||||
#include "movegen.h"
|
|
||||||
#include <limits.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
static int piece_value(Piece p)
|
|
||||||
{
|
|
||||||
switch (p)
|
|
||||||
{
|
|
||||||
case WK:
|
|
||||||
case BK:
|
|
||||||
return 0; /* king is invaluable; PST handled later if needed */
|
|
||||||
case WP:
|
|
||||||
case BP:
|
|
||||||
return 100;
|
|
||||||
case WN:
|
|
||||||
case BN:
|
|
||||||
return 320;
|
|
||||||
case WB:
|
|
||||||
case BB:
|
|
||||||
return 330;
|
|
||||||
case WR:
|
|
||||||
case BR:
|
|
||||||
return 500;
|
|
||||||
case WQ:
|
|
||||||
case BQ:
|
|
||||||
return 900;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int evaluate(const Position *pos)
|
|
||||||
{
|
|
||||||
int score = 0;
|
|
||||||
for (int sq = 0; sq < BOARD_SIZE; ++sq)
|
|
||||||
{
|
|
||||||
if ((sq & 0x88))
|
|
||||||
{
|
|
||||||
sq = (sq | 7);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Piece p = pos->board[sq];
|
|
||||||
int v = piece_value(p);
|
|
||||||
if (p >= WP && p <= WK)
|
|
||||||
{
|
|
||||||
score += v;
|
|
||||||
}
|
|
||||||
else if (p >= BP && p <= BK)
|
|
||||||
{
|
|
||||||
score -= v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Score from side-to-move perspective
|
|
||||||
return (pos->side == WHITE) ? score : -score;
|
|
||||||
}
|
|
||||||
|
|
||||||
int alphabeta(Position pos, int depth, int alpha, int beta, PrincipalVariation *pv)
|
|
||||||
{
|
|
||||||
if (depth <= 0)
|
|
||||||
{
|
|
||||||
return evaluate(&pos);
|
|
||||||
}
|
|
||||||
Move moves[256];
|
|
||||||
int n = gen_moves(&pos, moves, 256, 0);
|
|
||||||
if (n == 0)
|
|
||||||
{
|
|
||||||
// Checkmate or stalemate
|
|
||||||
if (in_check(&pos, pos.side))
|
|
||||||
{
|
|
||||||
return -30000 + (10 - depth); // checkmated
|
|
||||||
}
|
|
||||||
return 0; // stalemate
|
|
||||||
}
|
|
||||||
|
|
||||||
int best_score = INT_MIN / 2;
|
|
||||||
int best_from = -1;
|
|
||||||
int best_to = -1;
|
|
||||||
for (int i = 0; i < n; i++)
|
|
||||||
{
|
|
||||||
Position child = pos;
|
|
||||||
Piece cap = EMPTY;
|
|
||||||
make_move(&child, &moves[i], &cap);
|
|
||||||
int score = -alphabeta(child, depth - 1, -beta, -alpha, NULL);
|
|
||||||
if (score > best_score)
|
|
||||||
{
|
|
||||||
best_score = score;
|
|
||||||
best_from = moves[i].from;
|
|
||||||
best_to = moves[i].to;
|
|
||||||
}
|
|
||||||
if (best_score > alpha)
|
|
||||||
{
|
|
||||||
alpha = best_score;
|
|
||||||
}
|
|
||||||
if (alpha >= beta)
|
|
||||||
{
|
|
||||||
break; // beta cutoff
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pv != NULL)
|
|
||||||
{
|
|
||||||
pv->from = best_from;
|
|
||||||
pv->to = best_to;
|
|
||||||
}
|
|
||||||
return best_score;
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
#ifndef SEARCH_H
|
|
||||||
#define SEARCH_H
|
|
||||||
|
|
||||||
#include "movegen.h"
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
int depth;
|
|
||||||
int nodes;
|
|
||||||
} SearchLimits;
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
int from;
|
|
||||||
int to;
|
|
||||||
} PrincipalVariation;
|
|
||||||
|
|
||||||
// Evaluate position in centipawns from the side-to-move perspective.
|
|
||||||
int evaluate(const Position *pos);
|
|
||||||
|
|
||||||
// Negamax alpha-beta returning score in centipawns from side-to-move perspective.
|
|
||||||
int alphabeta(Position pos, int depth, int alpha, int beta, PrincipalVariation *pv);
|
|
||||||
|
|
||||||
#endif // SEARCH_H
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,190 +0,0 @@
|
|||||||
/*
|
|
||||||
* test_search.c - Unit tests for search.c (evaluate, alphabeta).
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "movegen.h"
|
|
||||||
#include "search.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
/* =========================================================================
|
|
||||||
* evaluate tests
|
|
||||||
* ========================================================================= */
|
|
||||||
|
|
||||||
static void test_evaluate_startpos_equal(void)
|
|
||||||
{
|
|
||||||
Position pos;
|
|
||||||
set_startpos(&pos);
|
|
||||||
/* Symmetric position: score from white perspective should be 0 */
|
|
||||||
assert(evaluate(&pos) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_evaluate_white_extra_queen(void)
|
|
||||||
{
|
|
||||||
Position pos;
|
|
||||||
/* White has an extra queen */
|
|
||||||
parse_fen(&pos, "4k3/8/8/8/8/8/8/Q3K3 w - - 0 1");
|
|
||||||
int score = evaluate(&pos);
|
|
||||||
assert(score > 0); /* White favored */
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_evaluate_black_extra_queen(void)
|
|
||||||
{
|
|
||||||
Position pos;
|
|
||||||
/* Black has an extra queen */
|
|
||||||
parse_fen(&pos, "4k1q1/8/8/8/8/8/8/4K3 w - - 0 1");
|
|
||||||
int score = evaluate(&pos);
|
|
||||||
assert(score < 0); /* Black favored from white's perspective */
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_evaluate_symmetric_material(void)
|
|
||||||
{
|
|
||||||
Position pos;
|
|
||||||
/* Equal material: rook vs rook, same side */
|
|
||||||
parse_fen(&pos, "4k2r/8/8/8/8/8/8/4K2R w - - 0 1");
|
|
||||||
assert(evaluate(&pos) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_evaluate_pawn_advantage(void)
|
|
||||||
{
|
|
||||||
Position pos;
|
|
||||||
/* White has 2 extra pawns */
|
|
||||||
parse_fen(&pos, "4k3/8/8/8/8/8/1PP5/4K3 w - - 0 1");
|
|
||||||
int white_score = evaluate(&pos);
|
|
||||||
pos.side = BLACK;
|
|
||||||
int black_score = evaluate(&pos);
|
|
||||||
assert(white_score > 0);
|
|
||||||
assert(black_score < 0); /* same position but from black's perspective */
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_evaluate_all_piece_types(void)
|
|
||||||
{
|
|
||||||
Position pos;
|
|
||||||
/* White: K+Q+R+B+N vs Black: K only */
|
|
||||||
parse_fen(&pos, "4k3/8/8/8/8/8/8/QRBN1K2 w - - 0 1");
|
|
||||||
int score = evaluate(&pos);
|
|
||||||
assert(score > 0);
|
|
||||||
/* White material: Q=900 + R=500 + B=330 + N=320 = 2050 */
|
|
||||||
assert(score == 900 + 500 + 330 + 320);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_evaluate_black_pieces(void)
|
|
||||||
{
|
|
||||||
Position pos;
|
|
||||||
/* Black: K+Q+R+B+N vs White: K only */
|
|
||||||
parse_fen(&pos, "4kqrb/4n3/8/8/8/8/8/4K3 w - - 0 1");
|
|
||||||
int score = evaluate(&pos);
|
|
||||||
/* From white perspective, should be heavily negative */
|
|
||||||
assert(score < -1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================================================================
|
|
||||||
* alphabeta tests
|
|
||||||
* ========================================================================= */
|
|
||||||
|
|
||||||
static void test_alphabeta_single_capture(void)
|
|
||||||
{
|
|
||||||
Position pos;
|
|
||||||
/* White rook can capture black queen - best move immediately obvious at depth 1 */
|
|
||||||
parse_fen(&pos, "4k3/8/8/3q4/3R4/8/8/4K3 w - - 0 1");
|
|
||||||
PrincipalVariation pv = {.from = -1, .to = -1};
|
|
||||||
int score = alphabeta(pos, 1, -30000, 30000, &pv);
|
|
||||||
assert(score > 0); /* Should find winning position */
|
|
||||||
/* Best move should be d4d5 (rook captures queen on d5) */
|
|
||||||
assert(pv.from >= 0);
|
|
||||||
assert(pv.to >= 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_alphabeta_checkmate_in_one(void)
|
|
||||||
{
|
|
||||||
Position pos;
|
|
||||||
/* White queen delivers checkmate at f7 */
|
|
||||||
parse_fen(&pos, "r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5Q2/PPPP1PPP/RNB1K1NR w KQkq - 4 4");
|
|
||||||
PrincipalVariation pv = {.from = -1, .to = -1};
|
|
||||||
int score = alphabeta(pos, 2, -30000, 30000, &pv);
|
|
||||||
/* Depth 2: should find the mating sequence */
|
|
||||||
(void)score;
|
|
||||||
assert(pv.from >= 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_alphabeta_stalemate_score(void)
|
|
||||||
{
|
|
||||||
Position pos;
|
|
||||||
/* Black king stalemated: ensure score is 0 (stalemate = draw)
|
|
||||||
* k on a8, white queen on c7, white king on c6 */
|
|
||||||
parse_fen(&pos, "k7/2Q5/2K5/8/8/8/8/8 b - - 0 1");
|
|
||||||
/* Black to move, stalemated */
|
|
||||||
PrincipalVariation pv = {.from = -1, .to = -1};
|
|
||||||
int score = alphabeta(pos, 1, -30000, 30000, &pv);
|
|
||||||
assert(score == 0); /* stalemate */
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_alphabeta_checkmate_score(void)
|
|
||||||
{
|
|
||||||
Position pos;
|
|
||||||
/* Black king checkmated (fool's mate) */
|
|
||||||
parse_fen(&pos, "rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 1 3");
|
|
||||||
/* White is mated; at depth 0 just evaluates material */
|
|
||||||
PrincipalVariation pv = {.from = -1, .to = -1};
|
|
||||||
int score = alphabeta(pos, 1, -30000, 30000, &pv);
|
|
||||||
/* White has no legal moves - should return very negative score */
|
|
||||||
assert(score < -20000);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_alphabeta_depth_zero(void)
|
|
||||||
{
|
|
||||||
Position pos;
|
|
||||||
set_startpos(&pos);
|
|
||||||
PrincipalVariation pv = {.from = -1, .to = -1};
|
|
||||||
int score = alphabeta(pos, 0, -30000, 30000, &pv);
|
|
||||||
/* At depth 0, returns leaf evaluation */
|
|
||||||
assert(score == 0); /* startpos is equal */
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_alphabeta_no_pv_null(void)
|
|
||||||
{
|
|
||||||
Position pos;
|
|
||||||
set_startpos(&pos);
|
|
||||||
/* Pass NULL for pv - should not crash */
|
|
||||||
int score = alphabeta(pos, 1, -30000, 30000, NULL);
|
|
||||||
(void)score;
|
|
||||||
/* Just checking no crash */
|
|
||||||
assert(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_alphabeta_beta_cutoff(void)
|
|
||||||
{
|
|
||||||
Position pos;
|
|
||||||
/* Need a position where beta cutoff fires: search at depth 2+ */
|
|
||||||
set_startpos(&pos);
|
|
||||||
PrincipalVariation pv = {.from = -1, .to = -1};
|
|
||||||
int score = alphabeta(pos, 2, -30000, 30000, &pv);
|
|
||||||
/* Symmetric start - should be near 0 */
|
|
||||||
(void)score;
|
|
||||||
assert(pv.from >= 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
/* evaluate */
|
|
||||||
test_evaluate_startpos_equal();
|
|
||||||
test_evaluate_white_extra_queen();
|
|
||||||
test_evaluate_black_extra_queen();
|
|
||||||
test_evaluate_symmetric_material();
|
|
||||||
test_evaluate_pawn_advantage();
|
|
||||||
test_evaluate_all_piece_types();
|
|
||||||
test_evaluate_black_pieces();
|
|
||||||
|
|
||||||
/* alphabeta */
|
|
||||||
test_alphabeta_single_capture();
|
|
||||||
test_alphabeta_checkmate_in_one();
|
|
||||||
test_alphabeta_stalemate_score();
|
|
||||||
test_alphabeta_checkmate_score();
|
|
||||||
test_alphabeta_depth_zero();
|
|
||||||
test_alphabeta_no_pv_null();
|
|
||||||
test_alphabeta_beta_cutoff();
|
|
||||||
|
|
||||||
printf("All tests passed (%d tests).\n", 14);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
CC := gcc
|
|
||||||
CFLAGS := -O2 -Wall -Wextra -std=c11
|
|
||||||
LDFLAGS :=
|
|
||||||
|
|
||||||
SRC := generatingWordsEndingWIthalka.c
|
|
||||||
BIN := generating_words
|
|
||||||
|
|
||||||
all: $(BIN)
|
|
||||||
|
|
||||||
$(BIN): $(SRC)
|
|
||||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
run: $(BIN)
|
|
||||||
./$(BIN)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(BIN)
|
|
||||||
|
|
||||||
.PHONY: all run clean
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
const int NUMBER_FOR_POLISH_SMALL_L = 136;
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
for (char i = 'a'; i < 'z' + 1; ++i)
|
|
||||||
{
|
|
||||||
printf("%ca%cka\n", i, NUMBER_FOR_POLISH_SMALL_L);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
4
C/misc/randomJPG/.gitignore
vendored
4
C/misc/randomJPG/.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
*.jpeg
|
|
||||||
*.jpg
|
|
||||||
generated_images*/*
|
|
||||||
generate_images
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
# Compiler
|
|
||||||
CC = gcc
|
|
||||||
|
|
||||||
# Compiler flags
|
|
||||||
CFLAGS = -Wall -O3 -march=native -flto -fomit-frame-pointer
|
|
||||||
|
|
||||||
# Libraries
|
|
||||||
LIBS = -ljpeg
|
|
||||||
|
|
||||||
# Source files
|
|
||||||
SRCS = generate_jpg.c
|
|
||||||
|
|
||||||
# Output executable
|
|
||||||
TARGET = generate_images
|
|
||||||
|
|
||||||
# Default target
|
|
||||||
all: $(TARGET)
|
|
||||||
|
|
||||||
# Link and compile the program
|
|
||||||
$(TARGET): $(SRCS)
|
|
||||||
$(CC) $(CFLAGS) -o $(TARGET) $(SRCS) $(LIBS)
|
|
||||||
|
|
||||||
# Clean up build artifacts
|
|
||||||
clean:
|
|
||||||
rm -f $(TARGET)
|
|
||||||
|
|
||||||
# Install the program (optional)
|
|
||||||
install: $(TARGET)
|
|
||||||
install -m 755 $(TARGET) /usr/local/bin/
|
|
||||||
|
|
||||||
.PHONY: all clean install
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
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,349 +0,0 @@
|
|||||||
#include <errno.h>
|
|
||||||
#include <jpeglib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
unsigned char r, g, b;
|
|
||||||
} RGB;
|
|
||||||
|
|
||||||
void print_usage(const char *program_name)
|
|
||||||
{
|
|
||||||
printf("Usage: %s [options] <num_images> <size> <block_size> <quality> <output_path> <format> "
|
|
||||||
"<color1> ... <colorN>\n",
|
|
||||||
program_name);
|
|
||||||
printf("Options:\n");
|
|
||||||
printf(" -h, --help Show this help message and exit\n");
|
|
||||||
printf("Arguments:\n");
|
|
||||||
printf(" <num_images> Number of images to generate (default: 1)\n");
|
|
||||||
printf(" <size> Size of each image (default: 1000)\n");
|
|
||||||
printf(" <block_size> Size of each block (default: 25)\n");
|
|
||||||
printf(" <quality> Quality of the output image (default: 100)\n");
|
|
||||||
printf(" <output_path> Path to save the output image (default: output.png)\n");
|
|
||||||
printf(" <format> Output format (jpeg or bmp, default: jpeg)\n");
|
|
||||||
printf(" <color1> ... <colorN> List of colors in hex format (default: #000000 and #FFFFFF)\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void create_folder_if_not_exists(const char *folder)
|
|
||||||
{
|
|
||||||
struct stat st = {0};
|
|
||||||
if (stat(folder, &st) == -1)
|
|
||||||
{
|
|
||||||
if (mkdir(folder, 0700) != 0)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Error creating directory: %s\n", strerror(errno));
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void generate_image_filename(char *unique_output_path, size_t size, const char *folder,
|
|
||||||
int image_index, const char *format)
|
|
||||||
{
|
|
||||||
snprintf(unique_output_path, size, "%s/bloated_image_%d.%s", folder, image_index, format);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char *allocate_image_buffer(int size)
|
|
||||||
{
|
|
||||||
unsigned char *image_buffer = malloc(size * size * 3);
|
|
||||||
if (!image_buffer)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Error allocating memory\n");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
return image_buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void fill_image_with_colors(unsigned char *image_buffer, int size, RGB *color_list, int num_colors,
|
|
||||||
int block_size)
|
|
||||||
{
|
|
||||||
for (int y = 0; y < size; y += block_size)
|
|
||||||
{
|
|
||||||
for (int x = 0; x < size; x += block_size)
|
|
||||||
{
|
|
||||||
RGB color = color_list[rand() % num_colors];
|
|
||||||
for (int i = 0; i < block_size; ++i)
|
|
||||||
{
|
|
||||||
for (int j = 0; j < block_size; ++j)
|
|
||||||
{
|
|
||||||
int index = ((y + i) * size + (x + j)) * 3;
|
|
||||||
image_buffer[index] = color.r;
|
|
||||||
image_buffer[index + 1] = color.g;
|
|
||||||
image_buffer[index + 2] = color.b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle_error(FILE *outfile, unsigned char *image_buffer)
|
|
||||||
{
|
|
||||||
if (!outfile)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Error opening output file: %s\n", strerror(errno));
|
|
||||||
free(image_buffer);
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup_compression(struct jpeg_compress_struct *cinfo, struct jpeg_error_mgr *jerr,
|
|
||||||
FILE *outfile, int size, int quality)
|
|
||||||
{
|
|
||||||
cinfo->err = jpeg_std_error(jerr);
|
|
||||||
jpeg_create_compress(cinfo);
|
|
||||||
jpeg_stdio_dest(cinfo, outfile);
|
|
||||||
|
|
||||||
cinfo->image_width = size;
|
|
||||||
cinfo->image_height = size;
|
|
||||||
cinfo->input_components = 3;
|
|
||||||
cinfo->in_color_space = JCS_RGB;
|
|
||||||
|
|
||||||
jpeg_set_defaults(cinfo);
|
|
||||||
jpeg_set_quality(cinfo, quality, TRUE);
|
|
||||||
jpeg_start_compress(cinfo, TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_scanlines(struct jpeg_compress_struct *cinfo, unsigned char *image_buffer, int size)
|
|
||||||
{
|
|
||||||
JSAMPROW row_pointer;
|
|
||||||
while (cinfo->next_scanline < cinfo->image_height)
|
|
||||||
{
|
|
||||||
row_pointer = (JSAMPROW)&image_buffer[cinfo->next_scanline * size * 3];
|
|
||||||
jpeg_write_scanlines(cinfo, &row_pointer, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void finalize_compression(struct jpeg_compress_struct *cinfo, FILE *outfile)
|
|
||||||
{
|
|
||||||
jpeg_finish_compress(cinfo);
|
|
||||||
fclose(outfile);
|
|
||||||
jpeg_destroy_compress(cinfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
void save_image_as_jpeg(unsigned char *image_buffer, int size, const char *unique_output_path,
|
|
||||||
int quality)
|
|
||||||
{
|
|
||||||
struct jpeg_compress_struct cinfo;
|
|
||||||
struct jpeg_error_mgr jerr;
|
|
||||||
FILE *outfile = fopen(unique_output_path, "wb");
|
|
||||||
|
|
||||||
handle_error(outfile, image_buffer);
|
|
||||||
setup_compression(&cinfo, &jerr, outfile, size, quality);
|
|
||||||
write_scanlines(&cinfo, image_buffer, size);
|
|
||||||
finalize_compression(&cinfo, outfile);
|
|
||||||
|
|
||||||
free(image_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void save_image_as_bmp(unsigned char *image_buffer, int size, const char *unique_output_path)
|
|
||||||
{
|
|
||||||
FILE *outfile = fopen(unique_output_path, "wb");
|
|
||||||
handle_error(outfile, image_buffer);
|
|
||||||
|
|
||||||
// BMP Header
|
|
||||||
unsigned char bmpfileheader[14] = {'B', 'M', 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0};
|
|
||||||
unsigned char bmpinfoheader[40] = {40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 24, 0};
|
|
||||||
int filesize = 54 + 3 * size * size;
|
|
||||||
|
|
||||||
bmpfileheader[2] = (unsigned char)(filesize);
|
|
||||||
bmpfileheader[3] = (unsigned char)(filesize >> 8);
|
|
||||||
bmpfileheader[4] = (unsigned char)(filesize >> 16);
|
|
||||||
bmpfileheader[5] = (unsigned char)(filesize >> 24);
|
|
||||||
|
|
||||||
bmpinfoheader[4] = (unsigned char)(size);
|
|
||||||
bmpinfoheader[5] = (unsigned char)(size >> 8);
|
|
||||||
bmpinfoheader[6] = (unsigned char)(size >> 16);
|
|
||||||
bmpinfoheader[7] = (unsigned char)(size >> 24);
|
|
||||||
bmpinfoheader[8] = (unsigned char)(size);
|
|
||||||
bmpinfoheader[9] = (unsigned char)(size >> 8);
|
|
||||||
bmpinfoheader[10] = (unsigned char)(size >> 16);
|
|
||||||
bmpinfoheader[11] = (unsigned char)(size >> 24);
|
|
||||||
|
|
||||||
fwrite(bmpfileheader, 1, 14, outfile);
|
|
||||||
fwrite(bmpinfoheader, 1, 40, outfile);
|
|
||||||
|
|
||||||
// Write image data (in BMP format, rows are bottom to top)
|
|
||||||
for (int y = size - 1; y >= 0; y--)
|
|
||||||
{
|
|
||||||
fwrite(image_buffer + (y * size * 3), 3, size, outfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(outfile);
|
|
||||||
free(image_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void generate_bloated_image(int size, RGB *color_list, int num_colors, int block_size,
|
|
||||||
const char *output_path, int quality, int image_index,
|
|
||||||
const char *folder, const char *format)
|
|
||||||
{
|
|
||||||
if (size % block_size != 0)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Size must be divisible by block_size\n");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
create_folder_if_not_exists(folder);
|
|
||||||
|
|
||||||
char unique_output_path[1024];
|
|
||||||
generate_image_filename(unique_output_path, sizeof(unique_output_path), folder, image_index,
|
|
||||||
format);
|
|
||||||
|
|
||||||
unsigned char *image_buffer = allocate_image_buffer(size);
|
|
||||||
|
|
||||||
fill_image_with_colors(image_buffer, size, color_list, num_colors, block_size);
|
|
||||||
|
|
||||||
if (strcmp(format, "jpeg") == 0)
|
|
||||||
{
|
|
||||||
save_image_as_jpeg(image_buffer, size, unique_output_path, quality);
|
|
||||||
}
|
|
||||||
else if (strcmp(format, "bmp") == 0)
|
|
||||||
{
|
|
||||||
save_image_as_bmp(image_buffer, size, unique_output_path);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Unsupported format: %s\n", format);
|
|
||||||
free(image_buffer);
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Image %d saved to %s\n", image_index, unique_output_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void allocate_color_list(int num_colors, RGB **color_list)
|
|
||||||
{
|
|
||||||
*color_list = malloc(num_colors * sizeof(RGB));
|
|
||||||
if (!(*color_list))
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Error allocating memory\n");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_single_color(const char *color_str, RGB *color)
|
|
||||||
{
|
|
||||||
unsigned int r, g, b;
|
|
||||||
if (sscanf(color_str, "#%02x%02x%02x", &r, &g, &b) != 3)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Invalid color format: %s\n", color_str);
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
*color = (RGB){r, g, b};
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_color_list(int argc, char *argv[], int *num_colors, RGB **color_list)
|
|
||||||
{
|
|
||||||
*num_colors = argc - 7;
|
|
||||||
allocate_color_list(*num_colors, color_list);
|
|
||||||
for (int i = 0; i < *num_colors; ++i)
|
|
||||||
{
|
|
||||||
parse_single_color(argv[7 + i], &(*color_list)[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_default_colors(int *num_colors, RGB **color_list)
|
|
||||||
{
|
|
||||||
const char *default_colors[] = {"#000000", "#FFFFFF", "#0000FF", "#00FF00",
|
|
||||||
"#00FFFF", "#FF0000", "#FF00FF", "#FFFF00"};
|
|
||||||
*num_colors = sizeof(default_colors) / sizeof(default_colors[0]);
|
|
||||||
allocate_color_list(*num_colors, color_list);
|
|
||||||
for (int i = 0; i < *num_colors; ++i)
|
|
||||||
{
|
|
||||||
parse_single_color(default_colors[i], &(*color_list)[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_colors(int argc, char *argv[], int *num_colors, RGB **color_list)
|
|
||||||
{
|
|
||||||
if (argc > 7)
|
|
||||||
{
|
|
||||||
parse_color_list(argc, argv, num_colors, color_list);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
parse_default_colors(num_colors, color_list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_arguments(int argc, char *argv[], int *num_images, int *size, int *block_size,
|
|
||||||
int *quality, const char **output_path, const char **format)
|
|
||||||
{
|
|
||||||
// Default values
|
|
||||||
*num_images = 1;
|
|
||||||
*size = 1000;
|
|
||||||
*block_size = 25;
|
|
||||||
*quality = 100;
|
|
||||||
*output_path = "output.png";
|
|
||||||
*format = "jpeg";
|
|
||||||
|
|
||||||
if (argc > 1)
|
|
||||||
*num_images = atoi(argv[1]);
|
|
||||||
if (argc > 2)
|
|
||||||
*size = atoi(argv[2]);
|
|
||||||
if (argc > 3)
|
|
||||||
*block_size = atoi(argv[3]);
|
|
||||||
if (argc > 4)
|
|
||||||
*quality = atoi(argv[4]);
|
|
||||||
if (argc > 5)
|
|
||||||
*output_path = argv[5];
|
|
||||||
if (argc > 6)
|
|
||||||
*format = argv[6];
|
|
||||||
}
|
|
||||||
|
|
||||||
void create_output_folder(char *folder, size_t folder_size)
|
|
||||||
{
|
|
||||||
time_t now = time(NULL);
|
|
||||||
struct tm *t = localtime(&now);
|
|
||||||
strftime(folder, folder_size, "generated_images_%Y%m%d_%H%M%S", t);
|
|
||||||
}
|
|
||||||
|
|
||||||
int handle_help_option(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
if (argc > 1 && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0))
|
|
||||||
{
|
|
||||||
print_usage(argv[0]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
srand(time(NULL));
|
|
||||||
if (handle_help_option(argc, argv))
|
|
||||||
{
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start time measurement
|
|
||||||
clock_t start_time = clock();
|
|
||||||
|
|
||||||
int num_images, size, block_size, quality;
|
|
||||||
const char *output_path, *format;
|
|
||||||
parse_arguments(argc, argv, &num_images, &size, &block_size, &quality, &output_path, &format);
|
|
||||||
|
|
||||||
RGB *color_list;
|
|
||||||
int num_colors;
|
|
||||||
parse_colors(argc, argv, &num_colors, &color_list);
|
|
||||||
|
|
||||||
char folder[64];
|
|
||||||
create_output_folder(folder, sizeof(folder));
|
|
||||||
|
|
||||||
for (int i = 1; i <= num_images; ++i)
|
|
||||||
{
|
|
||||||
generate_bloated_image(size, color_list, num_colors, block_size, output_path, quality, i,
|
|
||||||
folder, format);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(color_list);
|
|
||||||
|
|
||||||
// End time measurement
|
|
||||||
clock_t end_time = clock();
|
|
||||||
double execution_time = (double)(end_time - start_time) / CLOCKS_PER_SEC;
|
|
||||||
printf("Generated %d images in %f seconds!\n", num_images, execution_time);
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
@ -1,293 +0,0 @@
|
|||||||
#include <errno.h>
|
|
||||||
#include <jpeglib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
unsigned char r, g, b;
|
|
||||||
} RGB;
|
|
||||||
|
|
||||||
void print_usage(const char *program_name)
|
|
||||||
{
|
|
||||||
printf("Usage: %s [options] <num_images> <size> <block_size> <quality> <output_path> <color1> "
|
|
||||||
"... <colorN>\n",
|
|
||||||
program_name);
|
|
||||||
printf("Options:\n");
|
|
||||||
printf(" -h, --help Show this help message and exit\n");
|
|
||||||
printf("Arguments:\n");
|
|
||||||
printf(" <num_images> Number of images to generate (default: 1)\n");
|
|
||||||
printf(" <size> Size of each image (default: 1000)\n");
|
|
||||||
printf(" <block_size> Size of each block (default: 25)\n");
|
|
||||||
printf(" <quality> Quality of the output image (default: 100)\n");
|
|
||||||
printf(" <output_path> Path to save the output image (default: output.png)\n");
|
|
||||||
printf(" <color1> ... <colorN> List of colors in hex format (default: #000000 and #FFFFFF)\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void create_folder_if_not_exists(const char *folder)
|
|
||||||
{
|
|
||||||
struct stat st = {0};
|
|
||||||
if (stat(folder, &st) == -1)
|
|
||||||
{
|
|
||||||
if (mkdir(folder, 0700) != 0)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Error creating directory: %s\n", strerror(errno));
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void generate_image_filename(char *unique_output_path, size_t size, const char *folder,
|
|
||||||
int image_index)
|
|
||||||
{
|
|
||||||
snprintf(unique_output_path, size, "%s/bloated_image_%d.jpg", folder, image_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char *allocate_image_buffer(int size)
|
|
||||||
{
|
|
||||||
unsigned char *image_buffer = malloc(size * size * 3);
|
|
||||||
if (!image_buffer)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Error allocating memory\n");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
return image_buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void fill_image_with_colors(unsigned char *image_buffer, int size, RGB *color_list, int num_colors,
|
|
||||||
int block_size)
|
|
||||||
{
|
|
||||||
for (int y = 0; y < size; y += block_size)
|
|
||||||
{
|
|
||||||
for (int x = 0; x < size; x += block_size)
|
|
||||||
{
|
|
||||||
RGB color = color_list[rand() % num_colors];
|
|
||||||
for (int i = 0; i < block_size; ++i)
|
|
||||||
{
|
|
||||||
for (int j = 0; j < block_size; ++j)
|
|
||||||
{
|
|
||||||
int index = ((y + i) * size + (x + j)) * 3;
|
|
||||||
image_buffer[index] = color.r;
|
|
||||||
image_buffer[index + 1] = color.g;
|
|
||||||
image_buffer[index + 2] = color.b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle_error(FILE *outfile, unsigned char *image_buffer)
|
|
||||||
{
|
|
||||||
if (!outfile)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Error opening output file: %s\n", strerror(errno));
|
|
||||||
free(image_buffer);
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup_compression(struct jpeg_compress_struct *cinfo, struct jpeg_error_mgr *jerr,
|
|
||||||
FILE *outfile, int size, int quality)
|
|
||||||
{
|
|
||||||
cinfo->err = jpeg_std_error(jerr);
|
|
||||||
jpeg_create_compress(cinfo);
|
|
||||||
jpeg_stdio_dest(cinfo, outfile);
|
|
||||||
|
|
||||||
cinfo->image_width = size;
|
|
||||||
cinfo->image_height = size;
|
|
||||||
cinfo->input_components = 3;
|
|
||||||
cinfo->in_color_space = JCS_RGB;
|
|
||||||
|
|
||||||
jpeg_set_defaults(cinfo);
|
|
||||||
jpeg_set_quality(cinfo, quality, TRUE);
|
|
||||||
jpeg_start_compress(cinfo, TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_scanlines(struct jpeg_compress_struct *cinfo, unsigned char *image_buffer, int size)
|
|
||||||
{
|
|
||||||
JSAMPROW row_pointer;
|
|
||||||
while (cinfo->next_scanline < cinfo->image_height)
|
|
||||||
{
|
|
||||||
row_pointer = (JSAMPROW)&image_buffer[cinfo->next_scanline * size * 3];
|
|
||||||
jpeg_write_scanlines(cinfo, &row_pointer, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void finalize_compression(struct jpeg_compress_struct *cinfo, FILE *outfile)
|
|
||||||
{
|
|
||||||
jpeg_finish_compress(cinfo);
|
|
||||||
fclose(outfile);
|
|
||||||
jpeg_destroy_compress(cinfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
void save_image_as_jpeg(unsigned char *image_buffer, int size, const char *unique_output_path,
|
|
||||||
int quality)
|
|
||||||
{
|
|
||||||
struct jpeg_compress_struct cinfo;
|
|
||||||
struct jpeg_error_mgr jerr;
|
|
||||||
FILE *outfile = fopen(unique_output_path, "wb");
|
|
||||||
|
|
||||||
handle_error(outfile, image_buffer);
|
|
||||||
setup_compression(&cinfo, &jerr, outfile, size, quality);
|
|
||||||
write_scanlines(&cinfo, image_buffer, size);
|
|
||||||
finalize_compression(&cinfo, outfile);
|
|
||||||
|
|
||||||
free(image_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void generate_bloated_jpeg(int size, RGB *color_list, int num_colors, int block_size,
|
|
||||||
const char *output_path, int quality, int image_index,
|
|
||||||
const char *folder)
|
|
||||||
{
|
|
||||||
if (size % block_size != 0)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Size must be divisible by block_size\n");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
create_folder_if_not_exists(folder);
|
|
||||||
|
|
||||||
char unique_output_path[1024];
|
|
||||||
generate_image_filename(unique_output_path, sizeof(unique_output_path), folder, image_index);
|
|
||||||
|
|
||||||
unsigned char *image_buffer = allocate_image_buffer(size);
|
|
||||||
|
|
||||||
fill_image_with_colors(image_buffer, size, color_list, num_colors, block_size);
|
|
||||||
|
|
||||||
save_image_as_jpeg(image_buffer, size, unique_output_path, quality);
|
|
||||||
|
|
||||||
printf("Image %d saved to %s\n", image_index, unique_output_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void allocate_color_list(int num_colors, RGB **color_list)
|
|
||||||
{
|
|
||||||
*color_list = malloc(num_colors * sizeof(RGB));
|
|
||||||
if (!(*color_list))
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Error allocating memory\n");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_single_color(const char *color_str, RGB *color)
|
|
||||||
{
|
|
||||||
unsigned int r, g, b;
|
|
||||||
if (sscanf(color_str, "#%02x%02x%02x", &r, &g, &b) != 3)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Invalid color format: %s\n", color_str);
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
*color = (RGB){r, g, b};
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_color_list(int argc, char *argv[], int *num_colors, RGB **color_list)
|
|
||||||
{
|
|
||||||
*num_colors = argc - 6;
|
|
||||||
allocate_color_list(*num_colors, color_list);
|
|
||||||
for (int i = 0; i < *num_colors; ++i)
|
|
||||||
{
|
|
||||||
parse_single_color(argv[6 + i], &(*color_list)[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_default_colors(int *num_colors, RGB **color_list)
|
|
||||||
{
|
|
||||||
const char *default_colors[] = {"#000000", "#FFFFFF"};
|
|
||||||
*num_colors = sizeof(default_colors) / sizeof(default_colors[0]);
|
|
||||||
allocate_color_list(*num_colors, color_list);
|
|
||||||
for (int i = 0; i < *num_colors; ++i)
|
|
||||||
{
|
|
||||||
parse_single_color(default_colors[i], &(*color_list)[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_colors(int argc, char *argv[], int *num_colors, RGB **color_list)
|
|
||||||
{
|
|
||||||
if (argc > 6)
|
|
||||||
{
|
|
||||||
parse_color_list(argc, argv, num_colors, color_list);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
parse_default_colors(num_colors, color_list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_arguments(int argc, char *argv[], int *num_images, int *size, int *block_size,
|
|
||||||
int *quality, const char **output_path)
|
|
||||||
{
|
|
||||||
// Default values
|
|
||||||
*num_images = 1;
|
|
||||||
*size = 1000;
|
|
||||||
*block_size = 25;
|
|
||||||
*quality = 100;
|
|
||||||
*output_path = "output.png";
|
|
||||||
|
|
||||||
if (argc > 1)
|
|
||||||
*num_images = atoi(argv[1]);
|
|
||||||
if (argc > 2)
|
|
||||||
*size = atoi(argv[2]);
|
|
||||||
if (argc > 3)
|
|
||||||
*block_size = atoi(argv[3]);
|
|
||||||
if (argc > 4)
|
|
||||||
*quality = atoi(argv[4]);
|
|
||||||
if (argc > 5)
|
|
||||||
*output_path = argv[5];
|
|
||||||
}
|
|
||||||
|
|
||||||
void create_output_folder(char *folder, size_t folder_size)
|
|
||||||
{
|
|
||||||
time_t now = time(NULL);
|
|
||||||
struct tm *t = localtime(&now);
|
|
||||||
strftime(folder, folder_size, "generated_images_%Y%m%d_%H%M%S", t);
|
|
||||||
}
|
|
||||||
|
|
||||||
int handle_help_option(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
if (argc > 1 && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0))
|
|
||||||
{
|
|
||||||
print_usage(argv[0]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
srand(time(NULL));
|
|
||||||
if (handle_help_option(argc, argv))
|
|
||||||
{
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start time measurement
|
|
||||||
clock_t start_time = clock();
|
|
||||||
|
|
||||||
int num_images, size, block_size, quality;
|
|
||||||
const char *output_path;
|
|
||||||
parse_arguments(argc, argv, &num_images, &size, &block_size, &quality, &output_path);
|
|
||||||
|
|
||||||
RGB *color_list;
|
|
||||||
int num_colors;
|
|
||||||
parse_colors(argc, argv, &num_colors, &color_list);
|
|
||||||
|
|
||||||
char folder[64];
|
|
||||||
create_output_folder(folder, sizeof(folder));
|
|
||||||
|
|
||||||
for (int i = 1; i <= num_images; ++i)
|
|
||||||
{
|
|
||||||
generate_bloated_jpeg(size, color_list, num_colors, block_size, output_path, quality, i,
|
|
||||||
folder);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(color_list);
|
|
||||||
|
|
||||||
// End time measurement
|
|
||||||
clock_t end_time = clock();
|
|
||||||
double execution_time = (double)(end_time - start_time) / CLOCKS_PER_SEC;
|
|
||||||
printf("Generated %d images in %f seconds!\n", num_images, execution_time);
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
# Install dependencies
|
|
||||||
if command -v apt-get &>/dev/null; then
|
|
||||||
sudo apt-get install -y libjpeg-dev
|
|
||||||
elif command -v pacman &>/dev/null; then
|
|
||||||
pacman -Q libjpeg-turbo &>/dev/null || sudo pacman -S --noconfirm libjpeg-turbo
|
|
||||||
elif command -v dnf &>/dev/null; then
|
|
||||||
sudo dnf install -y libjpeg-turbo-devel
|
|
||||||
fi
|
|
||||||
make
|
|
||||||
./generate_images
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
make
|
|
||||||
./generating_words
|
|
||||||
7
C/misc/split/.gitignore
vendored
7
C/misc/split/.gitignore
vendored
@ -1,7 +0,0 @@
|
|||||||
split
|
|
||||||
test_split
|
|
||||||
*.gcda
|
|
||||||
*.gcno
|
|
||||||
*.gcov
|
|
||||||
coverage.info
|
|
||||||
coverage_html/
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
CC := gcc
|
|
||||||
CFLAGS := -O2 -Wall -Wextra -std=c11
|
|
||||||
LDFLAGS :=
|
|
||||||
|
|
||||||
SRC := main.c split.c
|
|
||||||
BIN := split
|
|
||||||
|
|
||||||
TEST_SRC := test_split.c split.c
|
|
||||||
TEST_BIN := test_split
|
|
||||||
|
|
||||||
COV_CFLAGS := -Wall -Wextra -std=c11 --coverage -g -O0
|
|
||||||
COV_LDFLAGS := -lm
|
|
||||||
|
|
||||||
all: $(BIN)
|
|
||||||
|
|
||||||
$(BIN): $(SRC)
|
|
||||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
run: $(BIN)
|
|
||||||
./$(BIN)
|
|
||||||
|
|
||||||
test: $(TEST_BIN)
|
|
||||||
./$(TEST_BIN)
|
|
||||||
|
|
||||||
$(TEST_BIN): $(TEST_SRC)
|
|
||||||
$(CC) $(CFLAGS) -o $@ $^ -lm
|
|
||||||
|
|
||||||
coverage:
|
|
||||||
$(CC) $(COV_CFLAGS) -o $(TEST_BIN) $(TEST_SRC) $(COV_LDFLAGS)
|
|
||||||
./$(TEST_BIN)
|
|
||||||
lcov --capture --directory . --output-file coverage.info --rc branch_coverage=1
|
|
||||||
lcov --remove coverage.info '/usr/*' --output-file coverage.info \
|
|
||||||
--rc branch_coverage=1 --ignore-errors unused
|
|
||||||
@LINE_PCT=$$(lcov --summary coverage.info 2>&1 | grep -oP 'lines\.*:\s*\K[0-9.]+'); \
|
|
||||||
echo "Line coverage: $${LINE_PCT}%"; \
|
|
||||||
if [ "$$(echo "$${LINE_PCT} < 100.0" | bc -l)" = "1" ]; then \
|
|
||||||
echo "FAIL: Line coverage $${LINE_PCT}% is below 100%"; \
|
|
||||||
exit 1; \
|
|
||||||
else \
|
|
||||||
echo "OK: 100% line coverage achieved"; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(BIN) $(TEST_BIN) *.gcda *.gcno *.gcov coverage.info
|
|
||||||
rm -rf coverage_html
|
|
||||||
|
|
||||||
.PHONY: all run test coverage clean
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#include "split.h"
|
|
||||||
|
|
||||||
// Example usage
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
int N = 5;
|
|
||||||
double X = 100;
|
|
||||||
double middle_value = 5.0;
|
|
||||||
double distances[5];
|
|
||||||
|
|
||||||
// Example usage for split_x_into_n_middle
|
|
||||||
split_x_into_n_middle(X, N, middle_value, distances);
|
|
||||||
|
|
||||||
printf("Split values (with middle value = %.2f):\n", middle_value);
|
|
||||||
for (int i = 0; i < N; i++)
|
|
||||||
{
|
|
||||||
printf("%.2f ", distances[i]);
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
|
|
||||||
// Example usage for split_x_into_n_symmetrically
|
|
||||||
double factors[2] = {1.0, 2.0};
|
|
||||||
split_x_into_n_symmetrically(X, N, factors, distances);
|
|
||||||
|
|
||||||
printf("Split values (symmetric with factors):\n");
|
|
||||||
for (int i = 0; i < N; i++)
|
|
||||||
{
|
|
||||||
printf("%.2f ", distances[i]);
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
make
|
|
||||||
./split
|
|
||||||
@ -1,80 +0,0 @@
|
|||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#include "split.h"
|
|
||||||
|
|
||||||
void calculate_symmetric_weights(int N, double middle_weight, const double *factors,
|
|
||||||
double *weights)
|
|
||||||
{
|
|
||||||
int half_N = N / 2;
|
|
||||||
int i = 0;
|
|
||||||
weights[half_N] = middle_weight;
|
|
||||||
|
|
||||||
if (factors)
|
|
||||||
{
|
|
||||||
for (i = 0; i < half_N; i++)
|
|
||||||
{
|
|
||||||
if (i == 0)
|
|
||||||
{
|
|
||||||
weights[half_N - i - 1] = middle_weight + factors[i];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
weights[half_N - i - 1] = weights[half_N - i] + factors[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (i = 0; i < half_N; i++)
|
|
||||||
{
|
|
||||||
weights[half_N - i - 1] = middle_weight - (i + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < half_N; i++)
|
|
||||||
{
|
|
||||||
weights[half_N + i + 1] = weights[half_N - i - 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void scale_to_total(double X, const double *weights, int N, double *distances)
|
|
||||||
{
|
|
||||||
double total_weight = 0;
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
for (i = 0; i < N; i++)
|
|
||||||
{
|
|
||||||
total_weight += weights[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
double base_unit = X / total_weight;
|
|
||||||
|
|
||||||
for (i = 0; i < N; i++)
|
|
||||||
{
|
|
||||||
distances[i] = base_unit * weights[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void split_x_into_n_symmetrically(double X, int N, double *factors, double *distances)
|
|
||||||
{
|
|
||||||
double *weights = (double *)malloc((size_t)N * sizeof(double));
|
|
||||||
if (!weights)
|
|
||||||
return;
|
|
||||||
|
|
||||||
calculate_symmetric_weights(N, 1.0, factors, weights);
|
|
||||||
scale_to_total(X, weights, N, distances);
|
|
||||||
|
|
||||||
free(weights);
|
|
||||||
}
|
|
||||||
|
|
||||||
void split_x_into_n_middle(double X, int N, double middle_value, double *distances)
|
|
||||||
{
|
|
||||||
double *weights = (double *)malloc((size_t)N * sizeof(double));
|
|
||||||
if (!weights)
|
|
||||||
return;
|
|
||||||
|
|
||||||
calculate_symmetric_weights(N, middle_value, NULL, weights);
|
|
||||||
scale_to_total(X, weights, N, distances);
|
|
||||||
|
|
||||||
free(weights);
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
#ifndef SPLIT_H
|
|
||||||
#define SPLIT_H
|
|
||||||
|
|
||||||
void calculate_symmetric_weights(int N, double middle_weight, const double *factors,
|
|
||||||
double *weights);
|
|
||||||
|
|
||||||
void scale_to_total(double X, const double *weights, int N, double *distances);
|
|
||||||
|
|
||||||
void split_x_into_n_symmetrically(double X, int N, double *factors, double *distances);
|
|
||||||
|
|
||||||
void split_x_into_n_middle(double X, int N, double middle_value, double *distances);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,214 +0,0 @@
|
|||||||
#include <assert.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#include "split.h"
|
|
||||||
|
|
||||||
#define EPSILON 1e-9
|
|
||||||
|
|
||||||
static void assert_close(double a, double b) { assert(fabs(a - b) < EPSILON); }
|
|
||||||
|
|
||||||
static double sum_array(const double *arr, int n)
|
|
||||||
{
|
|
||||||
double s = 0;
|
|
||||||
for (int i = 0; i < n; i++)
|
|
||||||
{
|
|
||||||
s += arr[i];
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* calculate_symmetric_weights: with factors, odd N */
|
|
||||||
static void test_symmetric_weights_with_factors_odd(void)
|
|
||||||
{
|
|
||||||
double weights[5];
|
|
||||||
double factors[2] = {1.0, 2.0};
|
|
||||||
|
|
||||||
calculate_symmetric_weights(5, 1.0, factors, weights);
|
|
||||||
|
|
||||||
/* middle = 1.0 */
|
|
||||||
assert_close(weights[2], 1.0);
|
|
||||||
/* i=0: weights[1] = middle + factors[0] = 2.0 */
|
|
||||||
assert_close(weights[1], 2.0);
|
|
||||||
/* i=1: weights[0] = weights[1] + factors[1] = 4.0 */
|
|
||||||
assert_close(weights[0], 4.0);
|
|
||||||
/* mirror: weights[3] = weights[1] = 2.0, weights[4] = weights[0] = 4.0 */
|
|
||||||
assert_close(weights[3], 2.0);
|
|
||||||
assert_close(weights[4], 4.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* calculate_symmetric_weights: with factors, even N */
|
|
||||||
static void test_symmetric_weights_with_factors_even(void)
|
|
||||||
{
|
|
||||||
double weights[4];
|
|
||||||
double factors[2] = {0.5, 1.5};
|
|
||||||
|
|
||||||
calculate_symmetric_weights(4, 3.0, factors, weights);
|
|
||||||
|
|
||||||
/* half_N = 2, middle index = 2 */
|
|
||||||
assert_close(weights[2], 3.0);
|
|
||||||
/* i=0: weights[1] = 3.0 + 0.5 = 3.5 */
|
|
||||||
assert_close(weights[1], 3.5);
|
|
||||||
/* i=1: weights[0] = weights[1] + 1.5 = 5.0 */
|
|
||||||
assert_close(weights[0], 5.0);
|
|
||||||
/* mirror: weights[3] = weights[1] = 3.5 */
|
|
||||||
assert_close(weights[3], 3.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* calculate_symmetric_weights: without factors (NULL), odd N */
|
|
||||||
static void test_symmetric_weights_null_factors_odd(void)
|
|
||||||
{
|
|
||||||
double weights[5];
|
|
||||||
|
|
||||||
calculate_symmetric_weights(5, 5.0, NULL, weights);
|
|
||||||
|
|
||||||
/* middle = 5.0 */
|
|
||||||
assert_close(weights[2], 5.0);
|
|
||||||
/* i=0: weights[1] = 5.0 - 1 = 4.0 */
|
|
||||||
assert_close(weights[1], 4.0);
|
|
||||||
/* i=1: weights[0] = 5.0 - 2 = 3.0 */
|
|
||||||
assert_close(weights[0], 3.0);
|
|
||||||
/* mirror */
|
|
||||||
assert_close(weights[3], 4.0);
|
|
||||||
assert_close(weights[4], 3.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* calculate_symmetric_weights: without factors, even N */
|
|
||||||
static void test_symmetric_weights_null_factors_even(void)
|
|
||||||
{
|
|
||||||
double weights[6];
|
|
||||||
|
|
||||||
calculate_symmetric_weights(6, 10.0, NULL, weights);
|
|
||||||
|
|
||||||
/* half_N = 3, middle index = 3 */
|
|
||||||
assert_close(weights[3], 10.0);
|
|
||||||
/* i=0: weights[2] = 10.0 - 1 = 9.0 */
|
|
||||||
assert_close(weights[2], 9.0);
|
|
||||||
/* i=1: weights[1] = 10.0 - 2 = 8.0 */
|
|
||||||
assert_close(weights[1], 8.0);
|
|
||||||
/* i=2: weights[0] = 10.0 - 3 = 7.0 */
|
|
||||||
assert_close(weights[0], 7.0);
|
|
||||||
/* mirror */
|
|
||||||
assert_close(weights[4], 9.0);
|
|
||||||
assert_close(weights[5], 8.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* calculate_symmetric_weights: N=1 (half_N=0, loops don't execute) */
|
|
||||||
static void test_symmetric_weights_n1(void)
|
|
||||||
{
|
|
||||||
double weights[1];
|
|
||||||
|
|
||||||
calculate_symmetric_weights(1, 42.0, NULL, weights);
|
|
||||||
assert_close(weights[0], 42.0);
|
|
||||||
|
|
||||||
double factors[1] = {99.0};
|
|
||||||
calculate_symmetric_weights(1, 7.0, factors, weights);
|
|
||||||
assert_close(weights[0], 7.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* scale_to_total: verify distances sum to X */
|
|
||||||
static void test_scale_to_total(void)
|
|
||||||
{
|
|
||||||
double weights[3] = {1.0, 2.0, 1.0};
|
|
||||||
double distances[3] = {0};
|
|
||||||
|
|
||||||
scale_to_total(100.0, weights, 3, distances);
|
|
||||||
|
|
||||||
assert_close(sum_array(distances, 3), 100.0);
|
|
||||||
/* total_weight = 4, base_unit = 25 */
|
|
||||||
assert_close(distances[0], 25.0);
|
|
||||||
assert_close(distances[1], 50.0);
|
|
||||||
assert_close(distances[2], 25.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* scale_to_total: single element */
|
|
||||||
static void test_scale_to_total_single(void)
|
|
||||||
{
|
|
||||||
double weights[1] = {5.0};
|
|
||||||
double distances[1] = {0};
|
|
||||||
|
|
||||||
scale_to_total(200.0, weights, 1, distances);
|
|
||||||
assert_close(distances[0], 200.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* split_x_into_n_symmetrically: N=5 with factors */
|
|
||||||
static void test_split_symmetrically(void)
|
|
||||||
{
|
|
||||||
double factors[2] = {1.0, 2.0};
|
|
||||||
double distances[5] = {0};
|
|
||||||
|
|
||||||
split_x_into_n_symmetrically(100.0, 5, factors, distances);
|
|
||||||
|
|
||||||
/* weights: [4, 2, 1, 2, 4] => total=13, base_unit=100/13 */
|
|
||||||
assert_close(sum_array(distances, 5), 100.0);
|
|
||||||
/* symmetry */
|
|
||||||
assert_close(distances[0], distances[4]);
|
|
||||||
assert_close(distances[1], distances[3]);
|
|
||||||
/* middle is smallest */
|
|
||||||
assert(distances[2] < distances[1]);
|
|
||||||
assert(distances[1] < distances[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* split_x_into_n_symmetrically: N=3 with factors */
|
|
||||||
static void test_split_symmetrically_n3(void)
|
|
||||||
{
|
|
||||||
double factors[1] = {2.0};
|
|
||||||
double distances[3] = {0};
|
|
||||||
|
|
||||||
split_x_into_n_symmetrically(60.0, 3, factors, distances);
|
|
||||||
|
|
||||||
assert_close(sum_array(distances, 3), 60.0);
|
|
||||||
assert_close(distances[0], distances[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* split_x_into_n_middle: N=5 with middle value */
|
|
||||||
static void test_split_middle(void)
|
|
||||||
{
|
|
||||||
double distances[5] = {0};
|
|
||||||
|
|
||||||
split_x_into_n_middle(100.0, 5, 5.0, distances);
|
|
||||||
|
|
||||||
assert_close(sum_array(distances, 5), 100.0);
|
|
||||||
/* symmetry */
|
|
||||||
assert_close(distances[0], distances[4]);
|
|
||||||
assert_close(distances[1], distances[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* split_x_into_n_middle: N=3 with middle value */
|
|
||||||
static void test_split_middle_n3(void)
|
|
||||||
{
|
|
||||||
double distances[3] = {0};
|
|
||||||
|
|
||||||
split_x_into_n_middle(90.0, 3, 10.0, distances);
|
|
||||||
|
|
||||||
assert_close(sum_array(distances, 3), 90.0);
|
|
||||||
assert_close(distances[0], distances[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* split_x_into_n_middle: N=1 */
|
|
||||||
static void test_split_middle_n1(void)
|
|
||||||
{
|
|
||||||
double distances[1] = {0};
|
|
||||||
|
|
||||||
split_x_into_n_middle(50.0, 1, 7.0, distances);
|
|
||||||
assert_close(distances[0], 50.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
test_symmetric_weights_with_factors_odd();
|
|
||||||
test_symmetric_weights_with_factors_even();
|
|
||||||
test_symmetric_weights_null_factors_odd();
|
|
||||||
test_symmetric_weights_null_factors_even();
|
|
||||||
test_symmetric_weights_n1();
|
|
||||||
test_scale_to_total();
|
|
||||||
test_scale_to_total_single();
|
|
||||||
test_split_symmetrically();
|
|
||||||
test_split_symmetrically_n3();
|
|
||||||
test_split_middle();
|
|
||||||
test_split_middle_n3();
|
|
||||||
test_split_middle_n1();
|
|
||||||
|
|
||||||
printf("All tests passed.\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
CC := gcc
|
|
||||||
CFLAGS := -O2 -Wall -Wextra -std=c11
|
|
||||||
|
|
||||||
# SDL2 flags: require sdl2-config (no fallback)
|
|
||||||
SDL2CONF := $(shell command -v sdl2-config 2>/dev/null)
|
|
||||||
ifeq ($(SDL2CONF),)
|
|
||||||
$(error sdl2-config not found. Please install SDL2 development package.)
|
|
||||||
endif
|
|
||||||
SDL_CFLAGS := $(shell sdl2-config --cflags)
|
|
||||||
SDL_LDFLAGS := $(shell sdl2-config --libs)
|
|
||||||
|
|
||||||
SRC := main.c gui.c engine.c chess.c mistakes.c
|
|
||||||
OBJ := $(SRC:.c=.o)
|
|
||||||
BIN := opening_learner
|
|
||||||
|
|
||||||
.PHONY: all clean run
|
|
||||||
|
|
||||||
all: $(BIN)
|
|
||||||
|
|
||||||
$(BIN): $(OBJ)
|
|
||||||
$(CC) $(CFLAGS) -o $@ $^ $(SDL_LDFLAGS)
|
|
||||||
|
|
||||||
%.o: %.c
|
|
||||||
$(CC) $(CFLAGS) $(SDL_CFLAGS) -c -o $@ $<
|
|
||||||
|
|
||||||
run: $(BIN)
|
|
||||||
./$(BIN)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(OBJ) $(BIN)
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
# Opening Learner (C + SDL2)
|
|
||||||
|
|
||||||
- Click a piece, then click a destination to move.
|
|
||||||
- Thick board outline, board uses non-pure colors.
|
|
||||||
- Uses local Stockfish or asmfish via UCI.
|
|
||||||
- Logs mistakes to `mistakes.txt` and lets you revisit them with the `m` key.
|
|
||||||
|
|
||||||
Build and check:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./check_build.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./opening_learner
|
|
||||||
```
|
|
||||||
|
|
||||||
Tips:
|
|
||||||
|
|
||||||
- ESC clears selection.
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
|
|
||||||
- Rendering avoids TTF dependency; pieces are clear, high-contrast geometric glyphs.
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
|
|
||||||
echo "Checking for engine (stockfish or asmfish)"
|
|
||||||
if command -v stockfish >/dev/null 2>&1; then
|
|
||||||
echo "Found stockfish"
|
|
||||||
elif command -v asmfish >/dev/null 2>&1; then
|
|
||||||
echo "Found asmfish"
|
|
||||||
else
|
|
||||||
echo "Error: Neither stockfish nor asmfish found in PATH." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Checking for SDL2 dev (sdl2-config)"
|
|
||||||
if command -v sdl2-config >/dev/null 2>&1; then
|
|
||||||
echo "Found sdl2-config"
|
|
||||||
else
|
|
||||||
echo "Error: sdl2-config not found. Install SDL2 dev (e.g., libsdl2-dev)." >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Building project"
|
|
||||||
make clean
|
|
||||||
make -j
|
|
||||||
|
|
||||||
echo "Build OK"
|
|
||||||
@ -1,670 +0,0 @@
|
|||||||
#include "chess.h"
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
static const int knight_offsets[8] = {15, 17, -15, -17, 10, -10, 6, -6};
|
|
||||||
static const int bishop_dirs[4] = {9, 7, -9, -7};
|
|
||||||
static const int rook_dirs[4] = {8, -8, 1, -1};
|
|
||||||
static const int king_dirs[8] = {8, -8, 1, -1, 9, 7, -9, -7};
|
|
||||||
|
|
||||||
static inline int file_of(int sq) { return sq % 8; }
|
|
||||||
static inline int rank_of(int sq) { return sq / 8; }
|
|
||||||
static inline bool on_board(int sq) { return sq >= 0 && sq < 64; }
|
|
||||||
static inline bool same_color(char a, char b)
|
|
||||||
{
|
|
||||||
return (isupper(a) && isupper(b)) || (islower(a) && islower(b));
|
|
||||||
}
|
|
||||||
static inline bool is_white(char p) { return isupper((unsigned char)p); }
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
int from;
|
|
||||||
int to;
|
|
||||||
char promo;
|
|
||||||
char captured;
|
|
||||||
} MoveCandidate;
|
|
||||||
|
|
||||||
void chess_init_start(Position *pos)
|
|
||||||
{
|
|
||||||
// a1..h1 (0..7) are white back rank; rank 8 at indexes 56..63 are black back rank
|
|
||||||
const char *start = "RNBQKBNRPPPPPPPP................................pppppppprnbqkbnr";
|
|
||||||
for (int i = 0; i < 64; i++)
|
|
||||||
pos->board[i] = start[i];
|
|
||||||
pos->white_to_move = true;
|
|
||||||
pos->castle_wk = pos->castle_wq = pos->castle_bk = pos->castle_bq = true;
|
|
||||||
pos->ep_square = -1;
|
|
||||||
pos->halfmove_clock = 0;
|
|
||||||
pos->fullmove_number = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void chess_copy(Position *dst, const Position *src) { *dst = *src; }
|
|
||||||
|
|
||||||
static bool is_empty(const Position *p, int sq) { return p->board[sq] == '.'; }
|
|
||||||
|
|
||||||
bool chess_square_attacked(const Position *pos, int sq, bool by_white)
|
|
||||||
{
|
|
||||||
// pawns
|
|
||||||
int r = rank_of(sq), f = file_of(sq);
|
|
||||||
if (by_white)
|
|
||||||
{
|
|
||||||
int s1 = (r - 1) * 8 + (f - 1);
|
|
||||||
if (f > 0 && r > 0 && on_board(s1) && pos->board[s1] == 'P')
|
|
||||||
return true;
|
|
||||||
int s2 = (r - 1) * 8 + (f + 1);
|
|
||||||
if (f < 7 && r > 0 && on_board(s2) && pos->board[s2] == 'P')
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int s1 = (r + 1) * 8 + (f - 1);
|
|
||||||
if (f > 0 && r < 7 && on_board(s1) && pos->board[s1] == 'p')
|
|
||||||
return true;
|
|
||||||
int s2 = (r + 1) * 8 + (f + 1);
|
|
||||||
if (f < 7 && r < 7 && on_board(s2) && pos->board[s2] == 'p')
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// knights
|
|
||||||
for (int i = 0; i < 8; i++)
|
|
||||||
{
|
|
||||||
int t = sq + knight_offsets[i];
|
|
||||||
if (!on_board(t))
|
|
||||||
continue;
|
|
||||||
int df = file_of(t) - f;
|
|
||||||
int dr = rank_of(t) - r;
|
|
||||||
if (df < -2 || df > 2 || dr < -2 || dr > 2)
|
|
||||||
continue; // edge wrap guard
|
|
||||||
char pc = pos->board[t];
|
|
||||||
if (by_white && pc == 'N')
|
|
||||||
return true;
|
|
||||||
if (!by_white && pc == 'n')
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// bishops/queens
|
|
||||||
for (int d = 0; d < 4; d++)
|
|
||||||
{
|
|
||||||
int off = bishop_dirs[d];
|
|
||||||
int t = sq + off;
|
|
||||||
while (on_board(t) && abs(file_of(t) - f) == abs(rank_of(t) - r))
|
|
||||||
{
|
|
||||||
char pc = pos->board[t];
|
|
||||||
if (pc != '.')
|
|
||||||
{
|
|
||||||
if (by_white && (pc == 'B' || pc == 'Q'))
|
|
||||||
return true;
|
|
||||||
if (!by_white && (pc == 'b' || pc == 'q'))
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
t += off;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// rooks/queens
|
|
||||||
for (int d = 0; d < 4; d++)
|
|
||||||
{
|
|
||||||
int off = rook_dirs[d];
|
|
||||||
int t = sq + off;
|
|
||||||
while (on_board(t) && (file_of(t) == f || rank_of(t) == r))
|
|
||||||
{
|
|
||||||
char pc = pos->board[t];
|
|
||||||
if (pc != '.')
|
|
||||||
{
|
|
||||||
if (by_white && (pc == 'R' || pc == 'Q'))
|
|
||||||
return true;
|
|
||||||
if (!by_white && (pc == 'r' || pc == 'q'))
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
t += off;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// king
|
|
||||||
for (int i = 0; i < 8; i++)
|
|
||||||
{
|
|
||||||
int t = sq + king_dirs[i];
|
|
||||||
if (!on_board(t))
|
|
||||||
continue;
|
|
||||||
if (abs(file_of(t) - f) > 1 || abs(rank_of(t) - r) > 1)
|
|
||||||
continue;
|
|
||||||
char pc = pos->board[t];
|
|
||||||
if (by_white && pc == 'K')
|
|
||||||
return true;
|
|
||||||
if (!by_white && pc == 'k')
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool chess_is_in_check(const Position *pos, bool white)
|
|
||||||
{
|
|
||||||
int ks = -1;
|
|
||||||
char k = white ? 'K' : 'k';
|
|
||||||
for (int i = 0; i < 64; i++)
|
|
||||||
if (pos->board[i] == k)
|
|
||||||
{
|
|
||||||
ks = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (ks == -1)
|
|
||||||
return false; // malformed
|
|
||||||
return chess_square_attacked(pos, ks, !white);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void add_move_if_legal(const Position *pos, MoveCandidate candidate, Move *out, size_t *n,
|
|
||||||
size_t max)
|
|
||||||
{
|
|
||||||
if (*n >= max)
|
|
||||||
return;
|
|
||||||
Position tmp;
|
|
||||||
chess_copy(&tmp, pos);
|
|
||||||
char captured_piece = candidate.captured ? candidate.captured : pos->board[candidate.to];
|
|
||||||
Move m = {0};
|
|
||||||
m.from = candidate.from;
|
|
||||||
m.to = candidate.to;
|
|
||||||
m.promo = candidate.promo;
|
|
||||||
m.moved = pos->board[candidate.from];
|
|
||||||
m.captured = captured_piece;
|
|
||||||
int prev_ep = tmp.ep_square;
|
|
||||||
m.prev_ep = prev_ep;
|
|
||||||
m.prev_wk = tmp.castle_wk;
|
|
||||||
m.prev_wq = tmp.castle_wq;
|
|
||||||
m.prev_bk = tmp.castle_bk;
|
|
||||||
m.prev_bq = tmp.castle_bq;
|
|
||||||
m.prev_halfmove = tmp.halfmove_clock;
|
|
||||||
if (!chess_make_move(&tmp, &m))
|
|
||||||
return;
|
|
||||||
if (chess_is_in_check(&tmp, !tmp.white_to_move))
|
|
||||||
return; // after make, side switched
|
|
||||||
out[(*n)++] = m; // store pseudo move with added flags from make_move
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t chess_generate_legal_moves(const Position *pos, Move *out, size_t max)
|
|
||||||
{
|
|
||||||
size_t n = 0;
|
|
||||||
bool white = pos->white_to_move;
|
|
||||||
for (int sq = 0; sq < 64; sq++)
|
|
||||||
{
|
|
||||||
char p = pos->board[sq];
|
|
||||||
if (p == '.')
|
|
||||||
continue;
|
|
||||||
if (white != is_white(p))
|
|
||||||
continue;
|
|
||||||
int f = file_of(sq), r = rank_of(sq);
|
|
||||||
switch (tolower(p))
|
|
||||||
{
|
|
||||||
case 'p':
|
|
||||||
{
|
|
||||||
int dir = white ? 8 : -8;
|
|
||||||
int start_rank = white ? 1 : 6;
|
|
||||||
int prom_rank = white ? 6 : 1;
|
|
||||||
int one = sq + dir;
|
|
||||||
if (on_board(one) && is_empty(pos, one))
|
|
||||||
{
|
|
||||||
if (r == prom_rank)
|
|
||||||
{
|
|
||||||
const char *pr = white ? "QRBN" : "qrbn";
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
add_move_if_legal(pos,
|
|
||||||
(MoveCandidate){.from = sq, .to = one, .promo = pr[i]},
|
|
||||||
out, &n, max);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = one, .promo = 0}, out,
|
|
||||||
&n, max);
|
|
||||||
}
|
|
||||||
// two
|
|
||||||
int two = sq + 2 * dir;
|
|
||||||
if (r == start_rank && is_empty(pos, two))
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = two, .promo = 0}, out,
|
|
||||||
&n, max);
|
|
||||||
}
|
|
||||||
// captures
|
|
||||||
int caps[2] = {dir + 1, dir - 1};
|
|
||||||
for (int i = 0; i < 2; i++)
|
|
||||||
{
|
|
||||||
int t = sq + caps[i];
|
|
||||||
if (!on_board(t))
|
|
||||||
continue;
|
|
||||||
if (abs(file_of(t) - f) != 1)
|
|
||||||
continue;
|
|
||||||
if (!is_empty(pos, t) && !same_color(pos->board[sq], pos->board[t]))
|
|
||||||
{
|
|
||||||
if (r == prom_rank)
|
|
||||||
{
|
|
||||||
const char *pr = white ? "QRBN" : "qrbn";
|
|
||||||
for (int j = 0; j < 4; j++)
|
|
||||||
add_move_if_legal(pos,
|
|
||||||
(MoveCandidate){.from = sq, .to = t, .promo = pr[j]},
|
|
||||||
out, &n, max);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0},
|
|
||||||
out, &n, max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// en passant
|
|
||||||
if (pos->ep_square != -1)
|
|
||||||
{
|
|
||||||
int ep = pos->ep_square;
|
|
||||||
if (abs(file_of(ep) - f) == 1 && (ep == sq + dir + 1 || ep == sq + dir - 1))
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = ep, .promo = 0}, out,
|
|
||||||
&n, max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'n':
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 8; i++)
|
|
||||||
{
|
|
||||||
int t = sq + knight_offsets[i];
|
|
||||||
if (!on_board(t))
|
|
||||||
continue;
|
|
||||||
if (abs(file_of(t) - f) > 2 || abs(rank_of(t) - r) > 2)
|
|
||||||
continue;
|
|
||||||
if (!is_empty(pos, t) && same_color(p, pos->board[t]))
|
|
||||||
continue;
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out, &n,
|
|
||||||
max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'b':
|
|
||||||
{
|
|
||||||
for (int d = 0; d < 4; d++)
|
|
||||||
{
|
|
||||||
int off = bishop_dirs[d];
|
|
||||||
int t = sq + off;
|
|
||||||
while (on_board(t) && abs(file_of(t) - f) == abs(rank_of(t) - r))
|
|
||||||
{
|
|
||||||
if (!is_empty(pos, t))
|
|
||||||
{
|
|
||||||
if (!same_color(p, pos->board[t]))
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0},
|
|
||||||
out, &n, max);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out,
|
|
||||||
&n, max);
|
|
||||||
t += off;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
{
|
|
||||||
for (int d = 0; d < 4; d++)
|
|
||||||
{
|
|
||||||
int off = rook_dirs[d];
|
|
||||||
int t = sq + off;
|
|
||||||
while (on_board(t) && (file_of(t) == f || rank_of(t) == r))
|
|
||||||
{
|
|
||||||
if (!is_empty(pos, t))
|
|
||||||
{
|
|
||||||
if (!same_color(p, pos->board[t]))
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0},
|
|
||||||
out, &n, max);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out,
|
|
||||||
&n, max);
|
|
||||||
t += off;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'q':
|
|
||||||
{
|
|
||||||
for (int d = 0; d < 4; d++)
|
|
||||||
{
|
|
||||||
int off = bishop_dirs[d];
|
|
||||||
int t = sq + off;
|
|
||||||
while (on_board(t) && abs(file_of(t) - f) == abs(rank_of(t) - r))
|
|
||||||
{
|
|
||||||
if (!is_empty(pos, t))
|
|
||||||
{
|
|
||||||
if (!same_color(p, pos->board[t]))
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0},
|
|
||||||
out, &n, max);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out,
|
|
||||||
&n, max);
|
|
||||||
t += off;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int d = 0; d < 4; d++)
|
|
||||||
{
|
|
||||||
int off = rook_dirs[d];
|
|
||||||
int t = sq + off;
|
|
||||||
while (on_board(t) && (file_of(t) == f || rank_of(t) == r))
|
|
||||||
{
|
|
||||||
if (!is_empty(pos, t))
|
|
||||||
{
|
|
||||||
if (!same_color(p, pos->board[t]))
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0},
|
|
||||||
out, &n, max);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out,
|
|
||||||
&n, max);
|
|
||||||
t += off;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'k':
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 8; i++)
|
|
||||||
{
|
|
||||||
int t = sq + king_dirs[i];
|
|
||||||
if (!on_board(t))
|
|
||||||
continue;
|
|
||||||
if (abs(file_of(t) - f) > 1 || abs(rank_of(t) - r) > 1)
|
|
||||||
continue;
|
|
||||||
if (!is_empty(pos, t) && same_color(p, pos->board[t]))
|
|
||||||
continue;
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = sq, .to = t, .promo = 0}, out, &n,
|
|
||||||
max);
|
|
||||||
}
|
|
||||||
// castling
|
|
||||||
if (white)
|
|
||||||
{
|
|
||||||
if (pos->castle_wk && pos->board[5] == '.' && pos->board[6] == '.' &&
|
|
||||||
!chess_square_attacked(pos, 4, false) &&
|
|
||||||
!chess_square_attacked(pos, 5, false) && !chess_square_attacked(pos, 6, false))
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = 4, .to = 6, .promo = 0}, out, &n,
|
|
||||||
max);
|
|
||||||
if (pos->castle_wq && pos->board[3] == '.' && pos->board[2] == '.' &&
|
|
||||||
pos->board[1] == '.' && !chess_square_attacked(pos, 4, false) &&
|
|
||||||
!chess_square_attacked(pos, 3, false) && !chess_square_attacked(pos, 2, false))
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = 4, .to = 2, .promo = 0}, out, &n,
|
|
||||||
max);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (pos->castle_bk && pos->board[61] == '.' && pos->board[62] == '.' &&
|
|
||||||
!chess_square_attacked(pos, 60, true) &&
|
|
||||||
!chess_square_attacked(pos, 61, true) && !chess_square_attacked(pos, 62, true))
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = 60, .to = 62, .promo = 0}, out,
|
|
||||||
&n, max);
|
|
||||||
if (pos->castle_bq && pos->board[59] == '.' && pos->board[58] == '.' &&
|
|
||||||
pos->board[57] == '.' && !chess_square_attacked(pos, 60, true) &&
|
|
||||||
!chess_square_attacked(pos, 59, true) && !chess_square_attacked(pos, 58, true))
|
|
||||||
add_move_if_legal(pos, (MoveCandidate){.from = 60, .to = 58, .promo = 0}, out,
|
|
||||||
&n, max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool chess_make_move(Position *pos, Move *m)
|
|
||||||
{
|
|
||||||
m->is_castle = false;
|
|
||||||
m->is_enpassant = false;
|
|
||||||
char p = pos->board[m->from];
|
|
||||||
char tgt = pos->board[m->to];
|
|
||||||
if (p == '.')
|
|
||||||
return false;
|
|
||||||
// handle special: en passant capture
|
|
||||||
if (tolower(p) == 'p' && m->to == pos->ep_square && file_of(m->to) != file_of(m->from) &&
|
|
||||||
tgt == '.')
|
|
||||||
{
|
|
||||||
m->is_enpassant = true;
|
|
||||||
int cap_sq = pos->white_to_move ? (m->to - 8) : (m->to + 8);
|
|
||||||
m->captured = pos->board[cap_sq];
|
|
||||||
pos->board[cap_sq] = '.';
|
|
||||||
}
|
|
||||||
|
|
||||||
// move piece
|
|
||||||
pos->board[m->to] = p;
|
|
||||||
pos->board[m->from] = '.';
|
|
||||||
|
|
||||||
// promotion
|
|
||||||
if (tolower(p) == 'p' && m->promo)
|
|
||||||
{
|
|
||||||
pos->board[m->to] = m->promo;
|
|
||||||
}
|
|
||||||
|
|
||||||
// castling rook move
|
|
||||||
if (tolower(p) == 'k')
|
|
||||||
{
|
|
||||||
int from = m->from, to = m->to;
|
|
||||||
if (from == 4 && to == 6)
|
|
||||||
{
|
|
||||||
pos->board[5] = 'R';
|
|
||||||
pos->board[7] = '.';
|
|
||||||
m->is_castle = true;
|
|
||||||
}
|
|
||||||
else if (from == 4 && to == 2)
|
|
||||||
{
|
|
||||||
pos->board[3] = 'R';
|
|
||||||
pos->board[0] = '.';
|
|
||||||
m->is_castle = true;
|
|
||||||
}
|
|
||||||
else if (from == 60 && to == 62)
|
|
||||||
{
|
|
||||||
pos->board[61] = 'r';
|
|
||||||
pos->board[63] = '.';
|
|
||||||
m->is_castle = true;
|
|
||||||
}
|
|
||||||
else if (from == 60 && to == 58)
|
|
||||||
{
|
|
||||||
pos->board[59] = 'r';
|
|
||||||
pos->board[56] = '.';
|
|
||||||
m->is_castle = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update castling rights
|
|
||||||
if (m->from == 0 || m->to == 0)
|
|
||||||
pos->castle_wq = false;
|
|
||||||
if (m->from == 7 || m->to == 7)
|
|
||||||
pos->castle_wk = false;
|
|
||||||
if (m->from == 56 || m->to == 56)
|
|
||||||
pos->castle_bq = false;
|
|
||||||
if (m->from == 63 || m->to == 63)
|
|
||||||
pos->castle_bk = false;
|
|
||||||
if (tolower(p) == 'k')
|
|
||||||
{
|
|
||||||
if (is_white(p))
|
|
||||||
{
|
|
||||||
pos->castle_wk = pos->castle_wq = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pos->castle_bk = pos->castle_bq = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update ep square
|
|
||||||
pos->ep_square = -1;
|
|
||||||
if (tolower(p) == 'p')
|
|
||||||
{
|
|
||||||
int df = rank_of(m->to) - rank_of(m->from);
|
|
||||||
if (df == 2 || df == -2)
|
|
||||||
{
|
|
||||||
pos->ep_square = (m->from + m->to) / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// halfmove clock
|
|
||||||
if (tolower(p) == 'p' || tgt != '.')
|
|
||||||
pos->halfmove_clock = 0;
|
|
||||||
else
|
|
||||||
pos->halfmove_clock++;
|
|
||||||
|
|
||||||
// side to move
|
|
||||||
pos->white_to_move = !pos->white_to_move;
|
|
||||||
if (pos->white_to_move)
|
|
||||||
pos->fullmove_number++;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void chess_unmake_move(Position *pos, const Move *m)
|
|
||||||
{
|
|
||||||
pos->white_to_move = !pos->white_to_move;
|
|
||||||
if (!pos->white_to_move)
|
|
||||||
pos->fullmove_number--;
|
|
||||||
// restore halfmove/flags
|
|
||||||
pos->ep_square = m->prev_ep;
|
|
||||||
pos->castle_wk = m->prev_wk;
|
|
||||||
pos->castle_wq = m->prev_wq;
|
|
||||||
pos->castle_bk = m->prev_bk;
|
|
||||||
pos->castle_bq = m->prev_bq;
|
|
||||||
pos->halfmove_clock = m->prev_halfmove;
|
|
||||||
|
|
||||||
char p = m->moved;
|
|
||||||
// undo promotions
|
|
||||||
if (tolower(p) == 'p' && m->promo)
|
|
||||||
{
|
|
||||||
p = is_white(p) ? 'P' : 'p';
|
|
||||||
}
|
|
||||||
|
|
||||||
pos->board[m->from] = p;
|
|
||||||
pos->board[m->to] = m->captured ? m->captured : '.';
|
|
||||||
if (m->is_enpassant)
|
|
||||||
{
|
|
||||||
int cap_sq = pos->white_to_move ? (m->to - 8) : (m->to + 8);
|
|
||||||
pos->board[m->to] = '.';
|
|
||||||
pos->board[cap_sq] = m->captured;
|
|
||||||
}
|
|
||||||
if (m->is_castle)
|
|
||||||
{
|
|
||||||
if (m->from == 4 && m->to == 6)
|
|
||||||
{
|
|
||||||
pos->board[7] = 'R';
|
|
||||||
pos->board[5] = '.';
|
|
||||||
}
|
|
||||||
else if (m->from == 4 && m->to == 2)
|
|
||||||
{
|
|
||||||
pos->board[0] = 'R';
|
|
||||||
pos->board[3] = '.';
|
|
||||||
}
|
|
||||||
else if (m->from == 60 && m->to == 62)
|
|
||||||
{
|
|
||||||
pos->board[63] = 'r';
|
|
||||||
pos->board[61] = '.';
|
|
||||||
}
|
|
||||||
else if (m->from == 60 && m->to == 58)
|
|
||||||
{
|
|
||||||
pos->board[56] = 'r';
|
|
||||||
pos->board[59] = '.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sq_to_coord(int sq, int *file, int *rank)
|
|
||||||
{
|
|
||||||
if (file)
|
|
||||||
*file = file_of(sq);
|
|
||||||
if (rank)
|
|
||||||
*rank = rank_of(sq);
|
|
||||||
}
|
|
||||||
int coord_to_sq(int file, int rank) { return rank * 8 + file; }
|
|
||||||
|
|
||||||
void move_to_uci(const Move *m, char buf[8])
|
|
||||||
{
|
|
||||||
int f1 = file_of(m->from), r1 = rank_of(m->from), f2 = file_of(m->to), r2 = rank_of(m->to);
|
|
||||||
buf[0] = (char)('a' + f1);
|
|
||||||
buf[1] = (char)('1' + r1);
|
|
||||||
buf[2] = (char)('a' + f2);
|
|
||||||
buf[3] = (char)('1' + r2);
|
|
||||||
int i = 4;
|
|
||||||
if (m->promo)
|
|
||||||
{
|
|
||||||
buf[i++] = (char)tolower((unsigned char)m->promo);
|
|
||||||
}
|
|
||||||
buf[i] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
bool parse_uci_move(const char *s, const Position *pos, Move *out)
|
|
||||||
{
|
|
||||||
if (!s || strlen(s) < 4)
|
|
||||||
return false;
|
|
||||||
int f1 = s[0] - 'a', r1 = s[1] - '1', f2 = s[2] - 'a', r2 = s[3] - '1';
|
|
||||||
if (f1 < 0 || f1 > 7 || f2 < 0 || f2 > 7 || r1 < 0 || r1 > 7 || r2 < 0 || r2 > 7)
|
|
||||||
return false;
|
|
||||||
int from = r1 * 8 + f1, to = r2 * 8 + f2;
|
|
||||||
char promo = s[4] ? s[4] : 0;
|
|
||||||
if (promo)
|
|
||||||
promo = pos->white_to_move ? toupper((unsigned char)promo) : tolower((unsigned char)promo);
|
|
||||||
Move list[MAX_MOVES];
|
|
||||||
size_t n = chess_generate_legal_moves(pos, list, MAX_MOVES);
|
|
||||||
for (size_t i = 0; i < n; i++)
|
|
||||||
{
|
|
||||||
if (list[i].from == from && list[i].to == to)
|
|
||||||
{
|
|
||||||
*out = list[i];
|
|
||||||
out->promo = promo ? promo : list[i].promo;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool chess_to_fen(const Position *pos, char *out, size_t outsz)
|
|
||||||
{
|
|
||||||
char buf[256];
|
|
||||||
int idx = 0;
|
|
||||||
for (int r = 7; r >= 0; r--)
|
|
||||||
{
|
|
||||||
int empty = 0;
|
|
||||||
for (int f = 0; f < 8; f++)
|
|
||||||
{
|
|
||||||
char p = pos->board[r * 8 + f];
|
|
||||||
if (p == '.')
|
|
||||||
empty++;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (empty)
|
|
||||||
{
|
|
||||||
buf[idx++] = (char)('0' + empty);
|
|
||||||
empty = 0;
|
|
||||||
}
|
|
||||||
buf[idx++] = p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (empty)
|
|
||||||
buf[idx++] = (char)('0' + empty);
|
|
||||||
if (r)
|
|
||||||
buf[idx++] = '/';
|
|
||||||
}
|
|
||||||
buf[idx++] = ' ';
|
|
||||||
buf[idx++] = pos->white_to_move ? 'w' : 'b';
|
|
||||||
buf[idx++] = ' ';
|
|
||||||
int start = idx;
|
|
||||||
if (pos->castle_wk)
|
|
||||||
buf[idx++] = 'K';
|
|
||||||
if (pos->castle_wq)
|
|
||||||
buf[idx++] = 'Q';
|
|
||||||
if (pos->castle_bk)
|
|
||||||
buf[idx++] = 'k';
|
|
||||||
if (pos->castle_bq)
|
|
||||||
buf[idx++] = 'q';
|
|
||||||
if (idx == start)
|
|
||||||
buf[idx++] = '-';
|
|
||||||
buf[idx++] = ' ';
|
|
||||||
if (pos->ep_square == -1)
|
|
||||||
{
|
|
||||||
buf[idx++] = '-';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int f = file_of(pos->ep_square), r = rank_of(pos->ep_square);
|
|
||||||
buf[idx++] = (char)('a' + f);
|
|
||||||
buf[idx++] = (char)('1' + r);
|
|
||||||
}
|
|
||||||
idx +=
|
|
||||||
snprintf(buf + idx, sizeof(buf) - idx, " %d %d", pos->halfmove_clock, pos->fullmove_number);
|
|
||||||
buf[idx] = '\0';
|
|
||||||
snprintf(out, outsz, "%s", buf);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
#ifndef CHESS_H
|
|
||||||
#define CHESS_H
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
// Board is 64 chars, a1=0, b1=1, ..., h8=63
|
|
||||||
// Pieces: 'P','N','B','R','Q','K' for white, lowercase for black, '.' empty
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
char board[64];
|
|
||||||
bool white_to_move;
|
|
||||||
bool castle_wk, castle_wq, castle_bk, castle_bq;
|
|
||||||
int ep_square; // -1 if none
|
|
||||||
int halfmove_clock;
|
|
||||||
int fullmove_number;
|
|
||||||
} Position;
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
int from, to;
|
|
||||||
char promo; // 0 or 'q','r','b','n' (lowercase for black)
|
|
||||||
char captured; // piece captured or 0
|
|
||||||
char moved; // piece moved
|
|
||||||
bool is_castle;
|
|
||||||
bool is_enpassant;
|
|
||||||
int prev_ep;
|
|
||||||
bool prev_wk, prev_wq, prev_bk, prev_bq;
|
|
||||||
int prev_halfmove;
|
|
||||||
} Move;
|
|
||||||
|
|
||||||
void chess_init_start(Position *pos);
|
|
||||||
void chess_copy(Position *dst, const Position *src);
|
|
||||||
|
|
||||||
// Move gen and make/unmake
|
|
||||||
size_t chess_generate_legal_moves(const Position *pos, Move *out, size_t max);
|
|
||||||
bool chess_make_move(Position *pos, Move *m);
|
|
||||||
void chess_unmake_move(Position *pos, const Move *m);
|
|
||||||
|
|
||||||
// Utility
|
|
||||||
bool chess_is_in_check(const Position *pos, bool white);
|
|
||||||
bool chess_square_attacked(const Position *pos, int sq, bool by_white);
|
|
||||||
|
|
||||||
// Conversions
|
|
||||||
void sq_to_coord(int sq, int *file, int *rank);
|
|
||||||
int coord_to_sq(int file, int rank);
|
|
||||||
|
|
||||||
// UCI strings like e2e4, with optional promotion char
|
|
||||||
void move_to_uci(const Move *m, char buf[8]);
|
|
||||||
bool parse_uci_move(const char *s, const Position *pos, Move *out);
|
|
||||||
|
|
||||||
// FEN
|
|
||||||
bool chess_to_fen(const Position *pos, char *out, size_t outsz);
|
|
||||||
|
|
||||||
#define MAX_MOVES 256
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,279 +0,0 @@
|
|||||||
#include "engine.h"
|
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
static void sleep_millis(unsigned int milliseconds)
|
|
||||||
{
|
|
||||||
struct timespec req = {
|
|
||||||
.tv_sec = milliseconds / 1000,
|
|
||||||
.tv_nsec = (long)(milliseconds % 1000) * 1000000L,
|
|
||||||
};
|
|
||||||
(void)nanosleep(&req, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool spawn_process(const char *path, Engine *e)
|
|
||||||
{
|
|
||||||
int inpipe[2], outpipe[2];
|
|
||||||
if (pipe(inpipe) < 0 || pipe(outpipe) < 0)
|
|
||||||
return false;
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid < 0)
|
|
||||||
return false;
|
|
||||||
if (pid == 0)
|
|
||||||
{
|
|
||||||
dup2(inpipe[0], STDIN_FILENO); // child stdin from inpipe[0]
|
|
||||||
dup2(outpipe[1], STDOUT_FILENO); // child stdout to outpipe[1]
|
|
||||||
dup2(outpipe[1], STDERR_FILENO);
|
|
||||||
close(inpipe[0]);
|
|
||||||
close(inpipe[1]);
|
|
||||||
close(outpipe[0]);
|
|
||||||
close(outpipe[1]);
|
|
||||||
execlp(path, path, (char *)NULL);
|
|
||||||
_exit(127);
|
|
||||||
}
|
|
||||||
// parent
|
|
||||||
close(inpipe[0]);
|
|
||||||
close(outpipe[1]);
|
|
||||||
e->pid = pid;
|
|
||||||
e->in_fd = inpipe[1];
|
|
||||||
e->out_fd = outpipe[0];
|
|
||||||
e->ready = false;
|
|
||||||
// make out_fd non-blocking for reads with polling
|
|
||||||
int flags = fcntl(e->out_fd, F_GETFL, 0);
|
|
||||||
fcntl(e->out_fd, F_SETFL, flags | O_NONBLOCK);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool try_start(Engine *e, const char *name)
|
|
||||||
{
|
|
||||||
if (!spawn_process(name, e))
|
|
||||||
return false;
|
|
||||||
// send UCI init
|
|
||||||
engine_cmd(e, "uci\n");
|
|
||||||
char buf[4096];
|
|
||||||
int attempts = 50; // ~5s total
|
|
||||||
while (attempts--)
|
|
||||||
{
|
|
||||||
sleep_millis(100);
|
|
||||||
ssize_t n = read(e->out_fd, buf, sizeof(buf) - 1);
|
|
||||||
if (n > 0)
|
|
||||||
{
|
|
||||||
buf[n] = '\0';
|
|
||||||
if (strstr(buf, "uciok"))
|
|
||||||
{
|
|
||||||
e->ready = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!e->ready)
|
|
||||||
{
|
|
||||||
engine_stop(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
engine_cmd(e, "isready\n");
|
|
||||||
attempts = 50;
|
|
||||||
while (attempts--)
|
|
||||||
{
|
|
||||||
sleep_millis(100);
|
|
||||||
ssize_t n = read(e->out_fd, buf, sizeof(buf) - 1);
|
|
||||||
if (n > 0)
|
|
||||||
{
|
|
||||||
buf[n] = '\0';
|
|
||||||
if (strstr(buf, "readyok"))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return e->ready;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool engine_start(Engine *e)
|
|
||||||
{
|
|
||||||
memset(e, 0, sizeof(*e));
|
|
||||||
e->pid = -1;
|
|
||||||
e->in_fd = -1;
|
|
||||||
e->out_fd = -1;
|
|
||||||
e->ready = false;
|
|
||||||
if (try_start(e, "stockfish"))
|
|
||||||
return true;
|
|
||||||
if (try_start(e, "asmfish"))
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void engine_stop(Engine *e)
|
|
||||||
{
|
|
||||||
if (e->in_fd != -1)
|
|
||||||
{
|
|
||||||
write(e->in_fd, "quit\n", 5);
|
|
||||||
close(e->in_fd);
|
|
||||||
}
|
|
||||||
if (e->out_fd != -1)
|
|
||||||
close(e->out_fd);
|
|
||||||
if (e->pid > 0)
|
|
||||||
{
|
|
||||||
int status;
|
|
||||||
waitpid(e->pid, &status, 0);
|
|
||||||
}
|
|
||||||
e->pid = -1;
|
|
||||||
e->in_fd = e->out_fd = -1;
|
|
||||||
e->ready = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool engine_cmd(Engine *e, const char *cmd)
|
|
||||||
{
|
|
||||||
if (e->in_fd == -1)
|
|
||||||
return false;
|
|
||||||
size_t len = strlen(cmd);
|
|
||||||
ssize_t n = write(e->in_fd, cmd, len);
|
|
||||||
if (n < 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return n == (ssize_t)len;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void position_to_uci(const Position *pos, char *out, size_t outsz)
|
|
||||||
{
|
|
||||||
// Send starting position and moves list by comparing with startpos; for simplicity, use FEN
|
|
||||||
// always.
|
|
||||||
char fen[256];
|
|
||||||
chess_to_fen(pos, fen, sizeof(fen));
|
|
||||||
(void)snprintf(out, outsz, "position fen %s\n", fen);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t engine_get_top_moves(Engine *e, const Position *pos, EngineMove *out, size_t max)
|
|
||||||
{
|
|
||||||
if (!e->ready)
|
|
||||||
return 0;
|
|
||||||
char cmd[512];
|
|
||||||
position_to_uci(pos, cmd, sizeof(cmd));
|
|
||||||
engine_cmd(e, cmd);
|
|
||||||
// ask multiPV up to max (cap at 5 as requested)
|
|
||||||
size_t req = max;
|
|
||||||
if (req > 5)
|
|
||||||
req = 5;
|
|
||||||
char go[128];
|
|
||||||
(void)snprintf(go, sizeof(go), "setoption name MultiPV value %zu\n", req);
|
|
||||||
engine_cmd(e, go);
|
|
||||||
engine_cmd(e, "go movetime 400\n");
|
|
||||||
char buf[8192];
|
|
||||||
size_t count = 0;
|
|
||||||
int attempts = 50;
|
|
||||||
while (attempts--)
|
|
||||||
{
|
|
||||||
sleep_millis(100);
|
|
||||||
ssize_t n = read(e->out_fd, buf, sizeof(buf) - 1);
|
|
||||||
if (n <= 0)
|
|
||||||
continue;
|
|
||||||
buf[n] = '\0';
|
|
||||||
char *line = strtok(buf, "\n");
|
|
||||||
while (line)
|
|
||||||
{
|
|
||||||
if (strncmp(line, "info ", 5) == 0)
|
|
||||||
{
|
|
||||||
// parse "info ... multipv X score cp Y ... pv <uci>"
|
|
||||||
char *mpv = strstr(line, " multipv ");
|
|
||||||
char *score = strstr(line, " score ");
|
|
||||||
char *pv = strstr(line, " pv ");
|
|
||||||
if (mpv && score && pv)
|
|
||||||
{
|
|
||||||
char *idx_end = NULL;
|
|
||||||
long idx_long = strtol(mpv + 9, &idx_end, 10);
|
|
||||||
if (idx_end == mpv + 9 || idx_long < 1 || (size_t)idx_long > req)
|
|
||||||
{
|
|
||||||
line = strtok(NULL, "\n");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
size_t idx = (size_t)idx_long;
|
|
||||||
if (idx >= 1 && idx <= req)
|
|
||||||
{
|
|
||||||
int cp = 0;
|
|
||||||
char *cp_loc = strstr(score, "cp ");
|
|
||||||
if (cp_loc)
|
|
||||||
{
|
|
||||||
char *cp_end = NULL;
|
|
||||||
long cp_long = strtol(cp_loc + 3, &cp_end, 10);
|
|
||||||
if (cp_end != cp_loc + 3 && cp_long >= INT_MIN && cp_long <= INT_MAX)
|
|
||||||
{
|
|
||||||
cp = (int)cp_long;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
char mv[8] = {0};
|
|
||||||
if (sscanf(pv + 4, "%7s", mv) != 1)
|
|
||||||
{
|
|
||||||
line = strtok(NULL, "\n");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
size_t i = idx - 1;
|
|
||||||
if (i < req)
|
|
||||||
{
|
|
||||||
out[i].score_cp = cp;
|
|
||||||
(void)snprintf(out[i].uci, sizeof(out[i].uci), "%s", mv);
|
|
||||||
if (i + 1 > count)
|
|
||||||
count = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (strncmp(line, "bestmove ", 9) == 0)
|
|
||||||
{
|
|
||||||
attempts = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
line = strtok(NULL, "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// simple sort by score descending (best to worst), keeping empties at end
|
|
||||||
for (size_t i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
for (size_t j = i + 1; j < count; j++)
|
|
||||||
{
|
|
||||||
if (out[j].score_cp > out[i].score_cp)
|
|
||||||
{
|
|
||||||
EngineMove tmp = out[i];
|
|
||||||
out[i] = out[j];
|
|
||||||
out[j] = tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool engine_get_best_move(Engine *e, const Position *pos, char out_uci[8])
|
|
||||||
{
|
|
||||||
if (!e->ready)
|
|
||||||
return false;
|
|
||||||
char cmd[512];
|
|
||||||
position_to_uci(pos, cmd, sizeof(cmd));
|
|
||||||
engine_cmd(e, cmd);
|
|
||||||
engine_cmd(e, "go movetime 300\n");
|
|
||||||
char buf[4096];
|
|
||||||
int attempts = 50;
|
|
||||||
while (attempts--)
|
|
||||||
{
|
|
||||||
sleep_millis(100);
|
|
||||||
ssize_t n = read(e->out_fd, buf, sizeof(buf) - 1);
|
|
||||||
if (n <= 0)
|
|
||||||
continue;
|
|
||||||
buf[n] = '\0';
|
|
||||||
char *line = strtok(buf, "\n");
|
|
||||||
while (line)
|
|
||||||
{
|
|
||||||
if (strncmp(line, "bestmove ", 9) == 0)
|
|
||||||
{
|
|
||||||
return sscanf(line + 9, "%7s", out_uci) == 1;
|
|
||||||
}
|
|
||||||
line = strtok(NULL, "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
#ifndef ENGINE_H
|
|
||||||
#define ENGINE_H
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
#include "chess.h"
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
int score_cp; // centipawns relative to side to move
|
|
||||||
char uci[8];
|
|
||||||
} EngineMove;
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
int pid;
|
|
||||||
int in_fd; // write to engine stdin
|
|
||||||
int out_fd; // read from engine stdout
|
|
||||||
bool ready;
|
|
||||||
} Engine;
|
|
||||||
|
|
||||||
// Start engine: tries stockfish, then asmfish. Returns false if none.
|
|
||||||
bool engine_start(Engine *e);
|
|
||||||
void engine_stop(Engine *e);
|
|
||||||
|
|
||||||
// Synchronous send command
|
|
||||||
bool engine_cmd(Engine *e, const char *cmd);
|
|
||||||
|
|
||||||
// Ask for top N moves from a position (short fixed time). Returns count.
|
|
||||||
size_t engine_get_top_moves(Engine *e, const Position *pos, EngineMove *out, size_t max);
|
|
||||||
|
|
||||||
// Ask for best move only.
|
|
||||||
bool engine_get_best_move(Engine *e, const Position *pos, char out_uci[8]);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,231 +0,0 @@
|
|||||||
#include "gui.h"
|
|
||||||
#include <SDL2/SDL.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
const SDL_Color COLOR_LIGHT = {238, 238, 210, 255}; // light square (not pure white)
|
|
||||||
const SDL_Color COLOR_DARK = {118, 150, 86, 255}; // dark square (not pure black)
|
|
||||||
const SDL_Color COLOR_GRID = {20, 20, 20, 255}; // thick outline
|
|
||||||
const SDL_Color COLOR_SEL = {200, 50, 50, 200}; // selection highlight
|
|
||||||
const SDL_Color COLOR_TEXT = {10, 10, 10, 255};
|
|
||||||
|
|
||||||
static void set_color(SDL_Renderer *r, SDL_Color c)
|
|
||||||
{
|
|
||||||
SDL_SetRenderDrawColor(r, c.r, c.g, c.b, c.a);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool gui_init(Gui *g, int w, int h, const char *title)
|
|
||||||
{
|
|
||||||
if (SDL_Init(SDL_INIT_VIDEO) != 0)
|
|
||||||
{
|
|
||||||
(void)fprintf(stderr, "SDL_Init error: %s\n", SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
g->window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w, h,
|
|
||||||
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
|
|
||||||
if (!g->window)
|
|
||||||
{
|
|
||||||
(void)fprintf(stderr, "SDL_CreateWindow error: %s\n", SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
g->renderer =
|
|
||||||
SDL_CreateRenderer(g->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
|
||||||
if (!g->renderer)
|
|
||||||
{
|
|
||||||
(void)fprintf(stderr, "SDL_CreateRenderer error: %s\n", SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
g->win_w = w;
|
|
||||||
g->win_h = h;
|
|
||||||
g->flipped = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void gui_destroy(Gui *g)
|
|
||||||
{
|
|
||||||
if (g->renderer)
|
|
||||||
SDL_DestroyRenderer(g->renderer);
|
|
||||||
if (g->window)
|
|
||||||
SDL_DestroyWindow(g->window);
|
|
||||||
SDL_Quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void gui_set_flipped(Gui *g, bool flipped) { g->flipped = flipped; }
|
|
||||||
|
|
||||||
static void draw_rect(SDL_Renderer *r, int x, int y, int w, int h, SDL_Color c)
|
|
||||||
{
|
|
||||||
set_color(r, c);
|
|
||||||
SDL_Rect rc = {x, y, w, h};
|
|
||||||
SDL_RenderFillRect(r, &rc);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void draw_outline(SDL_Renderer *r, SDL_Rect bounds, int thickness, SDL_Color c)
|
|
||||||
{
|
|
||||||
set_color(r, c);
|
|
||||||
for (int i = 0; i < thickness; i++)
|
|
||||||
{
|
|
||||||
SDL_Rect rc = {bounds.x + i, bounds.y + i, bounds.w - 2 * i, bounds.h - 2 * i};
|
|
||||||
SDL_RenderDrawRect(r, &rc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void draw_piece_letter(SDL_Renderer *r, SDL_Point origin, int cell_size, char piece)
|
|
||||||
{
|
|
||||||
// Minimal: draw a filled circle/square plus an initial letter approximated with rectangles.
|
|
||||||
// To keep dependencies minimal, no TTF. Ensure contrast: white pieces light, black pieces dark.
|
|
||||||
SDL_Color fill = (piece >= 'A' && piece <= 'Z') ? (SDL_Color){250, 250, 250, 255}
|
|
||||||
: (SDL_Color){30, 30, 30, 255};
|
|
||||||
SDL_Color glyph = (piece >= 'A' && piece <= 'Z') ? (SDL_Color){30, 30, 30, 255}
|
|
||||||
: (SDL_Color){240, 240, 240, 255};
|
|
||||||
// Base disk
|
|
||||||
int disk_offset = (int)(cell_size * 0.15f);
|
|
||||||
int disk_size = (int)(cell_size * 0.7f);
|
|
||||||
draw_rect(r, origin.x + disk_offset, origin.y + disk_offset, disk_size, disk_size, fill);
|
|
||||||
// Glyph: draw a simple letter-like mark
|
|
||||||
set_color(r, glyph);
|
|
||||||
// vertical bar
|
|
||||||
SDL_Rect bar1 = {origin.x + cell_size / 2 - cell_size / 16, origin.y + cell_size / 3,
|
|
||||||
cell_size / 8, cell_size / 3};
|
|
||||||
SDL_RenderFillRect(r, &bar1);
|
|
||||||
// top bar
|
|
||||||
SDL_Rect bar2 = {origin.x + cell_size / 3, origin.y + cell_size / 3 - cell_size / 10,
|
|
||||||
cell_size / 3, cell_size / 10};
|
|
||||||
SDL_RenderFillRect(r, &bar2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void gui_draw(Gui *g, const char board[64], const GuiSelection *sel, const char *status_line)
|
|
||||||
{
|
|
||||||
SDL_GetWindowSize(g->window, &g->win_w, &g->win_h);
|
|
||||||
|
|
||||||
set_color(g->renderer, (SDL_Color){35, 35, 35, 255});
|
|
||||||
SDL_RenderClear(g->renderer);
|
|
||||||
|
|
||||||
int size = (g->win_w < g->win_h ? g->win_w : g->win_h) - 40; // margins
|
|
||||||
if (size < 200)
|
|
||||||
size = 200;
|
|
||||||
int cell = size / 8;
|
|
||||||
size = cell * 8;
|
|
||||||
int ox = (g->win_w - size) / 2;
|
|
||||||
int oy = (g->win_h - size) / 2;
|
|
||||||
|
|
||||||
// Board outline (thick)
|
|
||||||
SDL_Rect board_bounds = {ox - 6, oy - 6, size + 12, size + 12};
|
|
||||||
draw_outline(g->renderer, board_bounds, 6, COLOR_GRID);
|
|
||||||
|
|
||||||
// Squares
|
|
||||||
for (int r = 0; r < 8; r++)
|
|
||||||
{
|
|
||||||
for (int f = 0; f < 8; f++)
|
|
||||||
{
|
|
||||||
int idx = g->flipped ? (63 - (r * 8 + f)) : (r * 8 + f);
|
|
||||||
SDL_Color c = ((r + f) & 1) ? COLOR_DARK : COLOR_LIGHT;
|
|
||||||
int cell_x = ox + f * cell;
|
|
||||||
int cell_y = oy + r * cell;
|
|
||||||
draw_rect(g->renderer, cell_x, cell_y, cell, cell, c);
|
|
||||||
|
|
||||||
char piece = board[idx];
|
|
||||||
if (piece != '.' && piece != '\0')
|
|
||||||
{
|
|
||||||
SDL_Point origin = {cell_x, cell_y};
|
|
||||||
draw_piece_letter(g->renderer, origin, cell, piece);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Selection overlay
|
|
||||||
if (sel && sel->clicked && sel->from_sq >= 0)
|
|
||||||
{
|
|
||||||
int s = sel->from_sq;
|
|
||||||
int rr = g->flipped ? 7 - (s / 8) : (s / 8);
|
|
||||||
int ff = g->flipped ? 7 - (s % 8) : (s % 8);
|
|
||||||
SDL_Rect sel_bounds = {ox + ff * cell + 2, oy + rr * cell + 2, cell - 4, cell - 4};
|
|
||||||
draw_outline(g->renderer, sel_bounds, 3, COLOR_SEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status strip
|
|
||||||
SDL_Rect status_bounds = {10, g->win_h - 40, g->win_w - 20, 30};
|
|
||||||
draw_outline(g->renderer, status_bounds, 2, COLOR_GRID);
|
|
||||||
// Without TTF, we can't render text; draw a minimal indicator bar to signal state.
|
|
||||||
// If status_line indicates success/failure, alter color.
|
|
||||||
SDL_Color bar = {80, 120, 200, 255};
|
|
||||||
if (status_line && status_line[0])
|
|
||||||
{
|
|
||||||
if (SDL_strstr(status_line, "Correct"))
|
|
||||||
bar = (SDL_Color){80, 200, 120, 255};
|
|
||||||
else if (SDL_strstr(status_line, "Wrong"))
|
|
||||||
bar = (SDL_Color){200, 80, 80, 255};
|
|
||||||
}
|
|
||||||
draw_rect(g->renderer, 12, g->win_h - 38, g->win_w - 24, 26, bar);
|
|
||||||
|
|
||||||
SDL_RenderPresent(g->renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
int gui_coord_to_sq(Gui *g, int x, int y)
|
|
||||||
{
|
|
||||||
int w, h;
|
|
||||||
SDL_GetWindowSize(g->window, &w, &h);
|
|
||||||
int size = (w < h ? w : h) - 40;
|
|
||||||
if (size < 200)
|
|
||||||
size = 200;
|
|
||||||
int cell = size / 8;
|
|
||||||
size = cell * 8;
|
|
||||||
int ox = (w - size) / 2;
|
|
||||||
int oy = (h - size) / 2;
|
|
||||||
if (x < ox || y < oy || x >= ox + size || y >= oy + size)
|
|
||||||
return -1;
|
|
||||||
int f = (x - ox) / cell;
|
|
||||||
int r = (y - oy) / cell;
|
|
||||||
int sq = r * 8 + f;
|
|
||||||
if (g->flipped)
|
|
||||||
sq = 63 - sq;
|
|
||||||
return sq;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool gui_poll_move(Gui *g, GuiSelection *sel, bool *quit_requested, int *key_out)
|
|
||||||
{
|
|
||||||
SDL_Event e;
|
|
||||||
bool updated = false;
|
|
||||||
if (key_out)
|
|
||||||
*key_out = 0;
|
|
||||||
while (SDL_PollEvent(&e))
|
|
||||||
{
|
|
||||||
if (e.type == SDL_QUIT)
|
|
||||||
{
|
|
||||||
if (quit_requested)
|
|
||||||
*quit_requested = true;
|
|
||||||
}
|
|
||||||
else if (e.type == SDL_WINDOWEVENT && e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
|
|
||||||
{
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
else if (e.type == SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_LEFT)
|
|
||||||
{
|
|
||||||
int sq = gui_coord_to_sq(g, e.button.x, e.button.y);
|
|
||||||
if (sq >= 0)
|
|
||||||
{
|
|
||||||
if (!sel->clicked)
|
|
||||||
{
|
|
||||||
sel->from_sq = sq;
|
|
||||||
sel->to_sq = -1;
|
|
||||||
sel->clicked = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sel->to_sq = sq;
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (e.type == SDL_KEYDOWN)
|
|
||||||
{
|
|
||||||
if (e.key.keysym.sym == SDLK_ESCAPE)
|
|
||||||
{
|
|
||||||
sel->clicked = false;
|
|
||||||
sel->from_sq = sel->to_sq = -1;
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
if (key_out)
|
|
||||||
*key_out = e.key.keysym.sym;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return updated;
|
|
||||||
}
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
#ifndef GUI_H
|
|
||||||
#define GUI_H
|
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
SDL_Window *window;
|
|
||||||
SDL_Renderer *renderer;
|
|
||||||
int win_w, win_h;
|
|
||||||
bool flipped; // true if black at bottom
|
|
||||||
} Gui;
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
int from_sq; // 0..63 or -1
|
|
||||||
int to_sq; // 0..63 or -1
|
|
||||||
char promo; // 'q','r','b','n' or 0
|
|
||||||
bool clicked;
|
|
||||||
} GuiSelection;
|
|
||||||
|
|
||||||
bool gui_init(Gui *g, int w, int h, const char *title);
|
|
||||||
void gui_destroy(Gui *g);
|
|
||||||
void gui_set_flipped(Gui *g, bool flipped);
|
|
||||||
void gui_draw(Gui *g, const char board[64], const GuiSelection *sel, const char *status_line);
|
|
||||||
// Returns true if something changed. If a key was pressed, key_out receives SDL_Keycode else 0.
|
|
||||||
bool gui_poll_move(Gui *g, GuiSelection *sel, bool *quit_requested, int *key_out);
|
|
||||||
int gui_coord_to_sq(Gui *g, int x, int y);
|
|
||||||
|
|
||||||
// colors
|
|
||||||
extern const SDL_Color COLOR_LIGHT;
|
|
||||||
extern const SDL_Color COLOR_DARK;
|
|
||||||
extern const SDL_Color COLOR_GRID;
|
|
||||||
extern const SDL_Color COLOR_SEL;
|
|
||||||
extern const SDL_Color COLOR_TEXT;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,283 +0,0 @@
|
|||||||
#include <signal.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#include "chess.h"
|
|
||||||
#include "engine.h"
|
|
||||||
#include "gui.h"
|
|
||||||
#include "mistakes.h"
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
Position pos;
|
|
||||||
Engine engine;
|
|
||||||
Gui gui;
|
|
||||||
MistakeList mistakes;
|
|
||||||
bool replay_mode; // allow revisiting mistakes
|
|
||||||
size_t replay_index;
|
|
||||||
} App;
|
|
||||||
|
|
||||||
static void append_uci(char *line, size_t sz, const char *mv)
|
|
||||||
{
|
|
||||||
if (line[0])
|
|
||||||
strncat(line, " ", sz - 1);
|
|
||||||
strncat(line, mv, sz - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void position_push_move_text(Position *pos, const Move *m, char *line, size_t lsz)
|
|
||||||
{
|
|
||||||
(void)pos;
|
|
||||||
char u[8];
|
|
||||||
move_to_uci(m, u);
|
|
||||||
append_uci(line, lsz, u);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void collect_all_legal_uci(const Position *pos, char list[][8], size_t *n, size_t max)
|
|
||||||
{
|
|
||||||
Move mv[MAX_MOVES];
|
|
||||||
size_t k = chess_generate_legal_moves(pos, mv, MAX_MOVES);
|
|
||||||
*n = 0;
|
|
||||||
for (size_t i = 0; i < k && *n < max; i++)
|
|
||||||
{
|
|
||||||
char u[8];
|
|
||||||
move_to_uci(&mv[i], u);
|
|
||||||
strncpy(list[(*n)++], u, 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool uci_in_list(const char *u, char list[][8], size_t n)
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < n; i++)
|
|
||||||
{
|
|
||||||
if (strncmp(u, list[i], 8) == 0)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *mistake_file_path(void) { return "mistakes.txt"; }
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
srand((unsigned)time(NULL));
|
|
||||||
|
|
||||||
App app;
|
|
||||||
memset(&app, 0, sizeof(app));
|
|
||||||
mistakes_init(&app.mistakes);
|
|
||||||
mistakes_load(&app.mistakes, mistake_file_path());
|
|
||||||
|
|
||||||
// Avoid SIGPIPE crashes when engine pipe closes
|
|
||||||
(void)signal(SIGPIPE, SIG_IGN);
|
|
||||||
|
|
||||||
if (!gui_init(&app.gui, 720, 760, "Opening Learner"))
|
|
||||||
{
|
|
||||||
(void)fprintf(stderr, "GUI init failed.\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!engine_start(&app.engine))
|
|
||||||
{
|
|
||||||
(void)fprintf(stderr,
|
|
||||||
"Error: Neither stockfish nor asmfish found locally. Please install one.\n");
|
|
||||||
gui_destroy(&app.gui);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize position
|
|
||||||
chess_init_start(&app.pos);
|
|
||||||
|
|
||||||
// Randomly pick side
|
|
||||||
bool player_is_white = rand() % 2 == 0;
|
|
||||||
gui_set_flipped(&app.gui, !player_is_white);
|
|
||||||
|
|
||||||
// gameplay state
|
|
||||||
char status[128] = "";
|
|
||||||
GuiSelection sel = {.from_sq = -1, .to_sq = -1, .promo = 0, .clicked = false};
|
|
||||||
char line_uci[512] = ""; // history of moves uci
|
|
||||||
|
|
||||||
// If white, player moves first
|
|
||||||
bool awaiting_player = player_is_white;
|
|
||||||
char expected_player_move[8] = ""; // best move suggested by engine for player
|
|
||||||
bool quit = false;
|
|
||||||
|
|
||||||
while (!quit)
|
|
||||||
{
|
|
||||||
// Show board
|
|
||||||
gui_draw(&app.gui, app.pos.board, &sel, status);
|
|
||||||
|
|
||||||
// Engine to act when it's engine turn
|
|
||||||
if (!awaiting_player)
|
|
||||||
{
|
|
||||||
// 2. Ask engine for proposed responses (5)
|
|
||||||
EngineMove props[5];
|
|
||||||
size_t n = engine_get_top_moves(&app.engine, &app.pos, props, 5);
|
|
||||||
// 3. Sort is done in engine; also collect legal ones not proposed
|
|
||||||
char legal[256][8];
|
|
||||||
size_t lcnt = 0;
|
|
||||||
collect_all_legal_uci(&app.pos, legal, &lcnt, 256);
|
|
||||||
// 4. pick response with decreasing probability
|
|
||||||
// add non-proposed at the end with minimal priority
|
|
||||||
char pool[300][8];
|
|
||||||
int weights[300];
|
|
||||||
size_t pcnt = 0;
|
|
||||||
for (size_t i = 0; i < n; i++)
|
|
||||||
{
|
|
||||||
strncpy(pool[pcnt], props[i].uci, 8);
|
|
||||||
weights[pcnt++] = (int)(n - i);
|
|
||||||
}
|
|
||||||
for (size_t i = 0; i < lcnt; i++)
|
|
||||||
{
|
|
||||||
if (!uci_in_list(legal[i], pool, pcnt))
|
|
||||||
{
|
|
||||||
memcpy(pool[pcnt], legal[i], 8);
|
|
||||||
pool[pcnt][7] = '\0';
|
|
||||||
weights[pcnt++] = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// weighted pick
|
|
||||||
int wsum = 0;
|
|
||||||
for (size_t i = 0; i < pcnt; i++)
|
|
||||||
wsum += weights[i];
|
|
||||||
int r = (wsum > 0) ? (rand() % wsum) : 0;
|
|
||||||
size_t pick = 0;
|
|
||||||
for (size_t i = 0; i < pcnt; i++)
|
|
||||||
{
|
|
||||||
if (r < weights[i])
|
|
||||||
{
|
|
||||||
pick = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
r -= weights[i];
|
|
||||||
}
|
|
||||||
// 5. play response
|
|
||||||
Move m;
|
|
||||||
if (!parse_uci_move(pool[pick], &app.pos, &m))
|
|
||||||
{ // fallback: best
|
|
||||||
if (!(n > 0 && parse_uci_move(props[0].uci, &app.pos, &m)))
|
|
||||||
{
|
|
||||||
(void)snprintf(status, sizeof(status), "No engine move");
|
|
||||||
quit = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chess_make_move(&app.pos, &m);
|
|
||||||
position_push_move_text(&app.pos, &m, line_uci, sizeof(line_uci));
|
|
||||||
awaiting_player = true;
|
|
||||||
// 6. Ask engine for optimal response from player
|
|
||||||
engine_get_best_move(&app.engine, &app.pos, expected_player_move);
|
|
||||||
(void)snprintf(status, sizeof(status), "Your turn");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Player move input
|
|
||||||
int key = 0;
|
|
||||||
bool updated = gui_poll_move(&app.gui, &sel, &quit, &key);
|
|
||||||
if (quit)
|
|
||||||
break;
|
|
||||||
if (key == 'm' || key == 'M')
|
|
||||||
{
|
|
||||||
// enter simple replay: load a mistake line and restart from it to the position where
|
|
||||||
// best move is required
|
|
||||||
if (app.mistakes.count > 0)
|
|
||||||
{
|
|
||||||
if (app.replay_index >= app.mistakes.count)
|
|
||||||
app.replay_index = 0;
|
|
||||||
Mistake *mk = &app.mistakes.items[app.replay_index++];
|
|
||||||
// reset and play the line moves to reach the mistake position
|
|
||||||
chess_init_start(&app.pos);
|
|
||||||
char tmp[512];
|
|
||||||
(void)snprintf(tmp, sizeof(tmp), "%s", mk->line);
|
|
||||||
char *tok = strtok(tmp, " ");
|
|
||||||
while (tok)
|
|
||||||
{
|
|
||||||
Move m;
|
|
||||||
if (parse_uci_move(tok, &app.pos, &m))
|
|
||||||
chess_make_move(&app.pos, &m);
|
|
||||||
tok = strtok(NULL, " ");
|
|
||||||
}
|
|
||||||
(void)snprintf(status, sizeof(status), "Practice: best is %s", mk->best_move);
|
|
||||||
strncpy(expected_player_move, mk->best_move, sizeof(expected_player_move));
|
|
||||||
awaiting_player = true;
|
|
||||||
gui_set_flipped(
|
|
||||||
&app.gui,
|
|
||||||
!app.pos.white_to_move); // if it's black to move, flip so black bottom
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (updated && sel.clicked && sel.to_sq >= 0)
|
|
||||||
{
|
|
||||||
Move list[MAX_MOVES];
|
|
||||||
size_t n = chess_generate_legal_moves(&app.pos, list, MAX_MOVES);
|
|
||||||
bool moved = false;
|
|
||||||
Move chosen = {0};
|
|
||||||
for (size_t i = 0; i < n; i++)
|
|
||||||
{
|
|
||||||
if (list[i].from == sel.from_sq && list[i].to == sel.to_sq)
|
|
||||||
{
|
|
||||||
chosen = list[i];
|
|
||||||
moved = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sel.clicked = false;
|
|
||||||
sel.from_sq = sel.to_sq = -1;
|
|
||||||
sel.promo = 0;
|
|
||||||
if (!moved)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// 7. Compare with expected move
|
|
||||||
char uci[8];
|
|
||||||
move_to_uci(&chosen, uci);
|
|
||||||
bool correct = (expected_player_move[0] && strncmp(uci, expected_player_move, 8) == 0);
|
|
||||||
if (correct)
|
|
||||||
{
|
|
||||||
chess_make_move(&app.pos, &chosen);
|
|
||||||
position_push_move_text(&app.pos, &chosen, line_uci, sizeof(line_uci));
|
|
||||||
(void)snprintf(status, sizeof(status), "Correct");
|
|
||||||
awaiting_player = false; // engine move next
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// log mistake: 1) save all moves that lead to mistake, 2) allow revisit later, 3)
|
|
||||||
// reset position
|
|
||||||
char fen[256];
|
|
||||||
chess_to_fen(&app.pos, fen, sizeof(fen));
|
|
||||||
MistakeEntry entry = {
|
|
||||||
.fen = fen,
|
|
||||||
.best_move = expected_player_move,
|
|
||||||
.line = line_uci,
|
|
||||||
};
|
|
||||||
mistakes_add(&app.mistakes, &entry);
|
|
||||||
(void)mistakes_save(&app.mistakes, mistake_file_path());
|
|
||||||
(void)snprintf(status, sizeof(status), "Wrong, best was %s", expected_player_move);
|
|
||||||
// redo best move to show
|
|
||||||
Move best;
|
|
||||||
if (parse_uci_move(expected_player_move, &app.pos, &best))
|
|
||||||
{
|
|
||||||
chess_make_move(&app.pos, &best);
|
|
||||||
position_push_move_text(&app.pos, &best, line_uci, sizeof(line_uci));
|
|
||||||
}
|
|
||||||
gui_draw(&app.gui, app.pos.board, &sel, status);
|
|
||||||
SDL_Delay(600);
|
|
||||||
// reset to start
|
|
||||||
chess_init_start(&app.pos);
|
|
||||||
line_uci[0] = '\0';
|
|
||||||
// randomize side again
|
|
||||||
player_is_white = rand() % 2 == 0;
|
|
||||||
gui_set_flipped(&app.gui, !player_is_white);
|
|
||||||
awaiting_player = player_is_white;
|
|
||||||
expected_player_move[0] = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Delay(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
mistakes_save(&app.mistakes, mistake_file_path());
|
|
||||||
gui_destroy(&app.gui);
|
|
||||||
engine_stop(&app.engine);
|
|
||||||
mistakes_free(&app.mistakes);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@ -1,102 +0,0 @@
|
|||||||
#include "mistakes.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
void mistakes_init(MistakeList *ml)
|
|
||||||
{
|
|
||||||
ml->items = NULL;
|
|
||||||
ml->count = 0;
|
|
||||||
ml->cap = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void mistakes_free(MistakeList *ml)
|
|
||||||
{
|
|
||||||
free(ml->items);
|
|
||||||
ml->items = NULL;
|
|
||||||
ml->count = ml->cap = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ensure_cap(MistakeList *ml, size_t need)
|
|
||||||
{
|
|
||||||
if (need <= ml->cap)
|
|
||||||
return;
|
|
||||||
size_t ncap = ml->cap ? ml->cap * 2 : 16;
|
|
||||||
while (ncap < need)
|
|
||||||
ncap *= 2;
|
|
||||||
Mistake *ni = realloc(ml->items, ncap * sizeof(Mistake));
|
|
||||||
if (!ni)
|
|
||||||
return; // OOM silently ignored
|
|
||||||
ml->items = ni;
|
|
||||||
ml->cap = ncap;
|
|
||||||
}
|
|
||||||
|
|
||||||
void mistakes_add(MistakeList *ml, const MistakeEntry *entry)
|
|
||||||
{
|
|
||||||
ensure_cap(ml, ml->count + 1);
|
|
||||||
Mistake *m = &ml->items[ml->count++];
|
|
||||||
(void)snprintf(m->fen, sizeof(m->fen), "%s", entry->fen);
|
|
||||||
(void)snprintf(m->best_move, sizeof(m->best_move), "%s", entry->best_move);
|
|
||||||
(void)snprintf(m->line, sizeof(m->line), "%s", entry->line);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool mistakes_save(const MistakeList *ml, const char *path)
|
|
||||||
{
|
|
||||||
FILE *f = fopen(path, "w");
|
|
||||||
if (!f)
|
|
||||||
return false;
|
|
||||||
for (size_t i = 0; i < ml->count; i++)
|
|
||||||
{
|
|
||||||
const Mistake *m = &ml->items[i];
|
|
||||||
(void)fprintf(f, "FEN:%s\nBEST:%s\nLINE:%s\n.\n", m->fen, m->best_move, m->line);
|
|
||||||
}
|
|
||||||
(void)fclose(f);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool mistakes_load(MistakeList *ml, const char *path)
|
|
||||||
{
|
|
||||||
FILE *f = fopen(path, "r");
|
|
||||||
if (!f)
|
|
||||||
return false;
|
|
||||||
char buf[1024];
|
|
||||||
char fen[128] = "";
|
|
||||||
char best[16] = "";
|
|
||||||
char line[512] = "";
|
|
||||||
while (fgets(buf, sizeof(buf), f))
|
|
||||||
{
|
|
||||||
if (strncmp(buf, "FEN:", 4) == 0)
|
|
||||||
{
|
|
||||||
// copy up to 127 chars, strip newline
|
|
||||||
size_t l = strcspn(buf + 4, "\n");
|
|
||||||
if (l >= sizeof(fen))
|
|
||||||
l = sizeof(fen) - 1;
|
|
||||||
memcpy(fen, buf + 4, l);
|
|
||||||
fen[l] = '\0';
|
|
||||||
}
|
|
||||||
else if (strncmp(buf, "BEST:", 5) == 0)
|
|
||||||
{
|
|
||||||
size_t l = strcspn(buf + 5, "\n");
|
|
||||||
if (l >= sizeof(best))
|
|
||||||
l = sizeof(best) - 1;
|
|
||||||
memcpy(best, buf + 5, l);
|
|
||||||
best[l] = '\0';
|
|
||||||
}
|
|
||||||
else if (strncmp(buf, "LINE:", 5) == 0)
|
|
||||||
{
|
|
||||||
size_t l = strcspn(buf + 5, "\n");
|
|
||||||
if (l >= sizeof(line))
|
|
||||||
l = sizeof(line) - 1;
|
|
||||||
memcpy(line, buf + 5, l);
|
|
||||||
line[l] = '\0';
|
|
||||||
}
|
|
||||||
else if (buf[0] == '.')
|
|
||||||
{
|
|
||||||
MistakeEntry entry = {.fen = fen, .best_move = best, .line = line};
|
|
||||||
mistakes_add(ml, &entry);
|
|
||||||
fen[0] = best[0] = line[0] = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(void)fclose(f);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
#ifndef MISTAKES_H
|
|
||||||
#define MISTAKES_H
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
// A lightweight mistake store in memory + file persistence.
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
char fen[128];
|
|
||||||
char best_move[8];
|
|
||||||
// PGN-like ply list in UCI for context
|
|
||||||
char line[512];
|
|
||||||
} Mistake;
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
Mistake *items;
|
|
||||||
size_t count;
|
|
||||||
size_t cap;
|
|
||||||
} MistakeList;
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
const char *fen;
|
|
||||||
const char *best_move;
|
|
||||||
const char *line;
|
|
||||||
} MistakeEntry;
|
|
||||||
|
|
||||||
void mistakes_init(MistakeList *ml);
|
|
||||||
void mistakes_free(MistakeList *ml);
|
|
||||||
void mistakes_add(MistakeList *ml, const MistakeEntry *entry);
|
|
||||||
bool mistakes_save(const MistakeList *ml, const char *path);
|
|
||||||
bool mistakes_load(MistakeList *ml, const char *path);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
FEN:rnbqkbnr/pppp1ppp/4p3/8/8/N4N2/PPPPPPPP/R1BQKB1R b KQkq - 1 2
|
|
||||||
BEST:f8a3
|
|
||||||
LINE:g1f3 e7e6 b1a3
|
|
||||||
.
|
|
||||||
FEN:rnbqkbnr/pppppppp/8/8/8/7P/PPPPPPP1/RNBQKBNR b KQkq - 0 1
|
|
||||||
BEST:e7e5
|
|
||||||
LINE:h2h3
|
|
||||||
.
|
|
||||||
FEN:rnbqkbnr/pppp1ppp/8/4p3/8/4P3/PPPPKPPP/RNBQ1BNR b kq - 1 2
|
|
||||||
BEST:d7d5
|
|
||||||
LINE:e2e3 e7e5 e1e2
|
|
||||||
.
|
|
||||||
FEN:rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
|
|
||||||
BEST:
|
|
||||||
LINE:
|
|
||||||
.
|
|
||||||
FEN:rnbqkbnr/pppppppp/8/8/8/7N/PPPPPPPP/RNBQKB1R b KQkq - 1 1
|
|
||||||
BEST:e7e5
|
|
||||||
LINE:g1h3
|
|
||||||
.
|
|
||||||
FEN:rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
|
|
||||||
BEST:
|
|
||||||
LINE:
|
|
||||||
.
|
|
||||||
FEN:rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
|
|
||||||
BEST:
|
|
||||||
LINE:
|
|
||||||
.
|
|
||||||
Binary file not shown.
@ -1,43 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
|
|
||||||
# Install SDL2 dev if sdl2-config is missing; otherwise build and run.
|
|
||||||
|
|
||||||
if ! command -v sdl2-config >/dev/null 2>&1; then
|
|
||||||
echo "sdl2-config not found. Attempting to install SDL2 dev..."
|
|
||||||
if [ -f /etc/os-release ]; then
|
|
||||||
. /etc/os-release
|
|
||||||
case "$ID" in
|
|
||||||
ubuntu|debian|linuxmint|neon|pop)
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libsdl2-dev
|
|
||||||
;;
|
|
||||||
arch|manjaro|endeavouros)
|
|
||||||
pacman -Q sdl2 &>/dev/null || sudo pacman -S --noconfirm sdl2
|
|
||||||
;;
|
|
||||||
fedora)
|
|
||||||
sudo dnf install -y SDL2-devel
|
|
||||||
;;
|
|
||||||
opensuse*|sles)
|
|
||||||
sudo zypper install -y libSDL2-devel
|
|
||||||
;;
|
|
||||||
void)
|
|
||||||
sudo xbps-install -Sy SDL2-devel
|
|
||||||
;;
|
|
||||||
alpine)
|
|
||||||
sudo apk add sdl2-dev
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unsupported distro ($ID). Please install SDL2 dev manually and rerun." >&2
|
|
||||||
exit 3
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
else
|
|
||||||
echo "/etc/os-release not found; cannot auto-detect distro. Install SDL2 dev manually." >&2
|
|
||||||
exit 3
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
./check_build.sh
|
|
||||||
./opening_learner
|
|
||||||
65
C/scrapeWebsite/.gitignore
vendored
65
C/scrapeWebsite/.gitignore
vendored
@ -1,65 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
CC := gcc
|
|
||||||
CFLAGS := -O2 -Wall -Wextra -std=c11 -D_DEFAULT_SOURCE $(shell pkg-config --cflags libcurl libxml-2.0 2>/dev/null)
|
|
||||||
LDFLAGS := $(shell pkg-config --libs libcurl libxml-2.0 2>/dev/null)
|
|
||||||
|
|
||||||
SRC := scrape.c
|
|
||||||
BIN := scrape
|
|
||||||
|
|
||||||
all: $(BIN)
|
|
||||||
|
|
||||||
$(BIN): $(SRC)
|
|
||||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
run: $(BIN)
|
|
||||||
./$(BIN)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(BIN)
|
|
||||||
|
|
||||||
.PHONY: all run clean
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
# Install dependencies
|
|
||||||
if command -v apt-get &>/dev/null; then
|
|
||||||
sudo apt-get install -y libcurl4-openssl-dev libxml2-dev
|
|
||||||
elif command -v pacman &>/dev/null; then
|
|
||||||
pacman -Q curl libxml2 &>/dev/null || sudo pacman -S --noconfirm curl libxml2
|
|
||||||
elif command -v dnf &>/dev/null; then
|
|
||||||
sudo dnf install -y libcurl-devel libxml2-devel
|
|
||||||
fi
|
|
||||||
make
|
|
||||||
./scrape
|
|
||||||
@ -1,206 +0,0 @@
|
|||||||
#include <curl/curl.h>
|
|
||||||
#include <libxml/HTMLparser.h>
|
|
||||||
#include <libxml/uri.h>
|
|
||||||
#include <libxml/xpath.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
// Structure to store downloaded data
|
|
||||||
struct MemoryStruct
|
|
||||||
{
|
|
||||||
char *memory;
|
|
||||||
size_t size;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Write callback function for curl
|
|
||||||
static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
|
|
||||||
{
|
|
||||||
size_t realsize = size * nmemb;
|
|
||||||
struct MemoryStruct *mem = (struct MemoryStruct *)userp;
|
|
||||||
|
|
||||||
char *ptr = realloc(mem->memory, mem->size + realsize + 1);
|
|
||||||
if (ptr == NULL)
|
|
||||||
{
|
|
||||||
printf("Not enough memory!\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
mem->memory = ptr;
|
|
||||||
memcpy(&(mem->memory[mem->size]), contents, realsize);
|
|
||||||
mem->size += realsize;
|
|
||||||
mem->memory[mem->size] = 0;
|
|
||||||
|
|
||||||
return realsize;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the curl request for the URL
|
|
||||||
CURL *init_curl_request(const char *url, struct MemoryStruct *chunk)
|
|
||||||
{
|
|
||||||
CURL *curl = curl_easy_init();
|
|
||||||
if (curl)
|
|
||||||
{
|
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)chunk);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
||||||
}
|
|
||||||
return curl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download the image file
|
|
||||||
int download_image(const char *url, const char *image_name)
|
|
||||||
{
|
|
||||||
if (access(image_name, F_OK) != -1)
|
|
||||||
{
|
|
||||||
printf("Image %s already exists, skipping download.\n", image_name);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
CURL *curl = curl_easy_init();
|
|
||||||
if (curl)
|
|
||||||
{
|
|
||||||
FILE *fp = fopen(image_name, "wb");
|
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
|
|
||||||
CURLcode res = curl_easy_perform(curl);
|
|
||||||
fclose(fp);
|
|
||||||
curl_easy_cleanup(curl);
|
|
||||||
return res == CURLE_OK ? 1 : 0;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse HTML and find the XPath expression
|
|
||||||
xmlChar *get_xpath_value(htmlDocPtr doc, const char *xpathExpr)
|
|
||||||
{
|
|
||||||
xmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc);
|
|
||||||
xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression((xmlChar *)xpathExpr, xpathCtx);
|
|
||||||
xmlChar *result = NULL;
|
|
||||||
|
|
||||||
if (xpathObj && !xmlXPathNodeSetIsEmpty(xpathObj->nodesetval))
|
|
||||||
{
|
|
||||||
result = xmlNodeListGetString(doc, xpathObj->nodesetval->nodeTab[0]->xmlChildrenNode, 1);
|
|
||||||
}
|
|
||||||
xmlXPathFreeObject(xpathObj);
|
|
||||||
xmlXPathFreeContext(xpathCtx);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the image URL and download it
|
|
||||||
void extract_and_download_image(htmlDocPtr doc, const char *url)
|
|
||||||
{
|
|
||||||
xmlChar *image_url = get_xpath_value(doc, "//*[@id='cc-comic']/@src");
|
|
||||||
if (image_url)
|
|
||||||
{
|
|
||||||
printf("Found image URL: %s\n", image_url);
|
|
||||||
char *image_name = strrchr((char *)image_url, '/');
|
|
||||||
if (image_name)
|
|
||||||
{
|
|
||||||
image_name++; // Skip the '/'
|
|
||||||
download_image((char *)image_url, image_name);
|
|
||||||
}
|
|
||||||
xmlFree(image_url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find and return the next button URL
|
|
||||||
char *find_next_button_url(htmlDocPtr doc)
|
|
||||||
{
|
|
||||||
xmlChar *next_url = get_xpath_value(doc, "//a[contains(@class,'cc-next')]/@href");
|
|
||||||
if (next_url)
|
|
||||||
{
|
|
||||||
char *url_copy = strdup((char *)next_url);
|
|
||||||
xmlFree(next_url);
|
|
||||||
return url_copy;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset chunk memory size before performing curl request
|
|
||||||
void reset_chunk_size(struct MemoryStruct *chunk) { chunk->size = 0; }
|
|
||||||
|
|
||||||
// Perform curl request and return result
|
|
||||||
CURLcode perform_curl_request(CURL *curl)
|
|
||||||
{
|
|
||||||
CURLcode res = curl_easy_perform(curl);
|
|
||||||
if (res != CURLE_OK)
|
|
||||||
{
|
|
||||||
printf("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the HTML document from the chunk memory
|
|
||||||
htmlDocPtr parse_html_from_chunk(struct MemoryStruct *chunk, const char *url)
|
|
||||||
{
|
|
||||||
return htmlReadMemory(chunk->memory, chunk->size, url, NULL,
|
|
||||||
HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle processing of the current HTML document
|
|
||||||
int process_html_document(htmlDocPtr doc, const char **url)
|
|
||||||
{
|
|
||||||
extract_and_download_image(doc, *url);
|
|
||||||
char *next_url = find_next_button_url(doc);
|
|
||||||
|
|
||||||
if (next_url)
|
|
||||||
{
|
|
||||||
printf("Next URL: %s\n", next_url);
|
|
||||||
*url = next_url;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
printf("Reached the end of images.\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up resources used during processing
|
|
||||||
void clean_up(CURL *curl, struct MemoryStruct *chunk)
|
|
||||||
{
|
|
||||||
curl_easy_cleanup(curl);
|
|
||||||
free(chunk->memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process the images and follow the next button
|
|
||||||
void process_images(const char *url)
|
|
||||||
{
|
|
||||||
struct MemoryStruct chunk = {malloc(1), 0};
|
|
||||||
CURL *curl = init_curl_request(url, &chunk);
|
|
||||||
CURLcode res;
|
|
||||||
|
|
||||||
if (curl)
|
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
reset_chunk_size(&chunk);
|
|
||||||
res = perform_curl_request(curl);
|
|
||||||
|
|
||||||
if (res != CURLE_OK)
|
|
||||||
break;
|
|
||||||
|
|
||||||
htmlDocPtr doc = parse_html_from_chunk(&chunk, url);
|
|
||||||
if (!doc)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (!process_html_document(doc, &url))
|
|
||||||
break;
|
|
||||||
|
|
||||||
xmlFreeDoc(doc);
|
|
||||||
} while (res == CURLE_OK);
|
|
||||||
|
|
||||||
clean_up(curl, &chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
const char *url = "..."; // Replace with your actual URL
|
|
||||||
process_images(url);
|
|
||||||
printf("All images processed.\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
CC := gcc
|
|
||||||
CFLAGS := -O2 -Wall -Wextra -std=c11 -D_DEFAULT_SOURCE
|
|
||||||
LDFLAGS :=
|
|
||||||
|
|
||||||
SRC := generatingPolishLettersOnWindowsTerminal.c
|
|
||||||
BIN := polish_letters
|
|
||||||
|
|
||||||
all: $(BIN)
|
|
||||||
|
|
||||||
$(BIN): $(SRC)
|
|
||||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
run: $(BIN)
|
|
||||||
./$(BIN)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(BIN)
|
|
||||||
|
|
||||||
.PHONY: all run clean
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#define SLEEP_S(s) Sleep((s) * 1000)
|
|
||||||
#else
|
|
||||||
#include <unistd.h>
|
|
||||||
#define SLEEP_S(s) sleep(s)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
printf("Henlo\n");
|
|
||||||
SLEEP_S(20);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
make
|
|
||||||
./polish_letters
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
CC = gcc
|
|
||||||
CFLAGS = -O3 -Wall -Wextra -march=native
|
|
||||||
COV = -O0 -g --coverage -Wall -Wextra
|
|
||||||
TARGET = vocabulary_curve
|
|
||||||
|
|
||||||
all: $(TARGET)
|
|
||||||
|
|
||||||
$(TARGET): main.c vocabulary.c vocabulary.h
|
|
||||||
$(CC) $(CFLAGS) -o $(TARGET) main.c vocabulary.c
|
|
||||||
|
|
||||||
# ---- tests ---------------------------------------------------------------
|
|
||||||
|
|
||||||
test_vocabulary: test_vocabulary.c vocabulary.c vocabulary.h
|
|
||||||
$(CC) $(COV) -o test_vocabulary test_vocabulary.c vocabulary.c
|
|
||||||
|
|
||||||
test: test_vocabulary
|
|
||||||
./test_vocabulary
|
|
||||||
|
|
||||||
# ---- coverage ------------------------------------------------------------
|
|
||||||
|
|
||||||
coverage: test_vocabulary
|
|
||||||
./test_vocabulary
|
|
||||||
lcov --capture --directory . --output-file coverage.info \
|
|
||||||
--rc branch_coverage=1 --ignore-errors inconsistent,mismatch,unused
|
|
||||||
lcov --extract coverage.info "$(CURDIR)/vocabulary.c" \
|
|
||||||
--output-file coverage.info \
|
|
||||||
--ignore-errors unused,inconsistent
|
|
||||||
@echo "--- Coverage Summary ---"
|
|
||||||
lcov --summary coverage.info --rc branch_coverage=1 2>&1 | tee /tmp/lcov_summary.txt
|
|
||||||
@LINE_COV=$$(grep "lines" /tmp/lcov_summary.txt | grep -oP '[0-9]+\.[0-9]+(?=%)'); \
|
|
||||||
echo "Line coverage: $${LINE_COV}%"; \
|
|
||||||
if [ "$$(echo "$${LINE_COV} < 100.0" | bc)" = "1" ]; then \
|
|
||||||
echo "FAIL: line coverage below 100%"; exit 1; \
|
|
||||||
fi
|
|
||||||
@echo "OK: 100% line coverage achieved"
|
|
||||||
|
|
||||||
run: $(TARGET)
|
|
||||||
./$(TARGET)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(TARGET) test_vocabulary *.gcda *.gcno *.gcov coverage.info
|
|
||||||
|
|
||||||
.PHONY: all run clean test coverage
|
|
||||||
@ -1,308 +0,0 @@
|
|||||||
/*
|
|
||||||
* Vocabulary Learning Curve Analyzer - thin driver.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "vocabulary.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/* Print excerpt words */
|
|
||||||
static void print_excerpt(const VocabContext *ctx, int start, int length)
|
|
||||||
{
|
|
||||||
for (int i = start; i < start + length; i++)
|
|
||||||
{
|
|
||||||
if (i > start)
|
|
||||||
printf(" ");
|
|
||||||
printf("%s", ctx->word_sequence[i]->word);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Print words needed (sorted by rank) */
|
|
||||||
static void print_words_needed(const VocabContext *ctx, int start, int length)
|
|
||||||
{
|
|
||||||
static WordEntry *unique_entries[MAX_UNIQUE_WORDS];
|
|
||||||
static bool seen_rank[MAX_UNIQUE_WORDS + 1];
|
|
||||||
memset(seen_rank, 0, (ctx->num_unique_words + 1) * sizeof(bool));
|
|
||||||
|
|
||||||
int count = 0;
|
|
||||||
for (int i = start; i < start + length; i++)
|
|
||||||
{
|
|
||||||
WordEntry *entry = ctx->word_sequence[i];
|
|
||||||
if (!seen_rank[entry->rank])
|
|
||||||
{
|
|
||||||
seen_rank[entry->rank] = true;
|
|
||||||
unique_entries[count++] = entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < count - 1; i++)
|
|
||||||
{
|
|
||||||
for (int j = i + 1; j < count; j++)
|
|
||||||
{
|
|
||||||
if (unique_entries[i]->rank > unique_entries[j]->rank)
|
|
||||||
{
|
|
||||||
WordEntry *tmp = unique_entries[i];
|
|
||||||
unique_entries[i] = unique_entries[j];
|
|
||||||
unique_entries[j] = tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
if (i > 0)
|
|
||||||
printf(", ");
|
|
||||||
printf("%s(#%d)", unique_entries[i]->word, unique_entries[i]->rank);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Print results */
|
|
||||||
static void print_results(const VocabContext *ctx, ExcerptResult *results, int max_length)
|
|
||||||
{
|
|
||||||
printf("======================================================================\n");
|
|
||||||
printf("VOCABULARY LEARNING CURVE\n");
|
|
||||||
printf("======================================================================\n");
|
|
||||||
printf("\n");
|
|
||||||
printf("For each excerpt length, the minimum number of top-frequency\n");
|
|
||||||
printf("words you need to learn to understand 100%%%% of some excerpt.\n");
|
|
||||||
printf("\n");
|
|
||||||
printf("Total words in text: %d\n", ctx->num_words);
|
|
||||||
printf("Unique words: %d\n", ctx->num_unique_words);
|
|
||||||
printf("\n");
|
|
||||||
printf("----------------------------------------------------------------------\n");
|
|
||||||
|
|
||||||
int prev_vocab = 0;
|
|
||||||
int actual_max = max_length;
|
|
||||||
if (actual_max > ctx->num_words)
|
|
||||||
actual_max = ctx->num_words;
|
|
||||||
|
|
||||||
for (int i = 0; i < actual_max; i++)
|
|
||||||
{
|
|
||||||
ExcerptResult *r = &results[i];
|
|
||||||
printf("\n[Length %d] Vocab needed: %d", r->excerpt_length, r->min_vocab_needed);
|
|
||||||
if (r->min_vocab_needed > prev_vocab)
|
|
||||||
printf(" (+%d)", r->min_vocab_needed - prev_vocab);
|
|
||||||
printf("\n");
|
|
||||||
printf(" Excerpt: \"");
|
|
||||||
print_excerpt(ctx, r->start_pos, r->excerpt_length);
|
|
||||||
printf("\"\n");
|
|
||||||
printf(" Words: ");
|
|
||||||
print_words_needed(ctx, r->start_pos, r->excerpt_length);
|
|
||||||
printf("\n");
|
|
||||||
prev_vocab = r->min_vocab_needed;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("\n----------------------------------------------------------------------\n");
|
|
||||||
|
|
||||||
if (actual_max > 0)
|
|
||||||
{
|
|
||||||
ExcerptResult *final = &results[actual_max - 1];
|
|
||||||
printf("\nTo understand a %d-word excerpt,\n", final->excerpt_length);
|
|
||||||
printf("you need to learn at minimum %d top words.\n", final->min_vocab_needed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dump_vocabulary(const VocabContext *ctx, int max_rank)
|
|
||||||
{
|
|
||||||
printf("VOCAB_DUMP_START\n");
|
|
||||||
for (int i = 0; i < ctx->num_unique_words; i++)
|
|
||||||
{
|
|
||||||
if (ctx->all_entries[i]->rank <= max_rank)
|
|
||||||
printf("%s;%d\n", ctx->all_entries[i]->word, ctx->all_entries[i]->rank);
|
|
||||||
}
|
|
||||||
printf("VOCAB_DUMP_END\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_longest_excerpt_result(const VocabContext *ctx, int max_vocab, int best_start,
|
|
||||||
int best_length)
|
|
||||||
{
|
|
||||||
printf("======================================================================\n");
|
|
||||||
printf("INVERSE MODE: LONGEST EXCERPT WITH TOP %d WORDS\n", max_vocab);
|
|
||||||
printf("======================================================================\n");
|
|
||||||
printf("\n");
|
|
||||||
printf("Total words in text: %d\n", ctx->num_words);
|
|
||||||
printf("Unique words: %d\n", ctx->num_unique_words);
|
|
||||||
printf("Vocabulary limit: top %d words\n", max_vocab);
|
|
||||||
printf("\n");
|
|
||||||
printf("----------------------------------------------------------------------\n");
|
|
||||||
printf("\n");
|
|
||||||
|
|
||||||
if (best_length == 0)
|
|
||||||
{
|
|
||||||
printf("No valid excerpt found with top %d words.\n", max_vocab);
|
|
||||||
printf("The text may require rarer words from the very beginning.\n");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
printf("LONGEST EXCERPT: %d words\n", best_length);
|
|
||||||
printf("Position: words %d to %d\n", best_start + 1, best_start + best_length);
|
|
||||||
printf("\n");
|
|
||||||
printf("Excerpt:\n \"");
|
|
||||||
print_excerpt(ctx, best_start, best_length);
|
|
||||||
printf("\"\n");
|
|
||||||
printf("\n");
|
|
||||||
|
|
||||||
int max_rank_used = 0;
|
|
||||||
const char *rarest_word = NULL;
|
|
||||||
for (int i = best_start; i < best_start + best_length; i++)
|
|
||||||
{
|
|
||||||
if (ctx->word_sequence[i]->rank > max_rank_used)
|
|
||||||
{
|
|
||||||
max_rank_used = ctx->word_sequence[i]->rank;
|
|
||||||
rarest_word = ctx->word_sequence[i]->word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// cppcheck-suppress nullPointer
|
|
||||||
printf("Rarest word used: %s (#%d)\n", rarest_word, max_rank_used);
|
|
||||||
|
|
||||||
static bool seen_rank[MAX_UNIQUE_WORDS + 1];
|
|
||||||
memset(seen_rank, 0, (ctx->num_unique_words + 1) * sizeof(bool));
|
|
||||||
int unique_count = 0;
|
|
||||||
for (int i = best_start; i < best_start + best_length; i++)
|
|
||||||
{
|
|
||||||
if (!seen_rank[ctx->word_sequence[i]->rank])
|
|
||||||
{
|
|
||||||
seen_rank[ctx->word_sequence[i]->rank] = true;
|
|
||||||
unique_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
printf("Unique words in excerpt: %d\n", unique_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("\n----------------------------------------------------------------------\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
if (argc < 2)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Usage: %s <file.txt> [options]\n", argv[0]);
|
|
||||||
fprintf(stderr, "\nModes:\n");
|
|
||||||
fprintf(stderr, " (default) Find minimum vocab needed for each excerpt length\n");
|
|
||||||
fprintf(stderr,
|
|
||||||
" --max-vocab N INVERSE: Find longest excerpt using only top N words\n");
|
|
||||||
fprintf(stderr, "\nOptions:\n");
|
|
||||||
fprintf(stderr, " max_length Maximum excerpt length to analyze (default: 30)\n");
|
|
||||||
fprintf(stderr, " --dump-vocab [N] Output all words with ranks up to N\n");
|
|
||||||
fprintf(stderr, "\nExamples:\n");
|
|
||||||
fprintf(stderr, " %s book.txt 50 # Analyze excerpts up to 50 words\n",
|
|
||||||
argv[0]);
|
|
||||||
fprintf(stderr, " %s book.txt --max-vocab 500 # Find longest excerpt with top 500 words\n",
|
|
||||||
argv[0]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *filename = argv[1];
|
|
||||||
int max_length = 30;
|
|
||||||
bool dump_vocab = false;
|
|
||||||
int dump_max_rank = 0;
|
|
||||||
int max_vocab_mode = 0;
|
|
||||||
|
|
||||||
for (int i = 2; i < argc; i++)
|
|
||||||
{
|
|
||||||
if (strcmp(argv[i], "--dump-vocab") == 0)
|
|
||||||
{
|
|
||||||
dump_vocab = true;
|
|
||||||
if (i + 1 < argc && argv[i + 1][0] != '-')
|
|
||||||
dump_max_rank = atoi(argv[++i]);
|
|
||||||
}
|
|
||||||
else if (strcmp(argv[i], "--max-vocab") == 0)
|
|
||||||
{
|
|
||||||
if (i + 1 < argc)
|
|
||||||
{
|
|
||||||
max_vocab_mode = atoi(argv[++i]);
|
|
||||||
if (max_vocab_mode < 1)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Error: --max-vocab requires a positive number\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Error: --max-vocab requires a number\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (argv[i][0] != '-')
|
|
||||||
{
|
|
||||||
max_length = atoi(argv[i]);
|
|
||||||
if (max_length < 1)
|
|
||||||
max_length = 1;
|
|
||||||
if (max_length > 1000)
|
|
||||||
max_length = 1000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VocabContext ctx;
|
|
||||||
vocab_init(&ctx);
|
|
||||||
|
|
||||||
FILE *fp = fopen(filename, "r");
|
|
||||||
if (!fp)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Cannot open file: %s\n", filename);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ok = vocab_process_stream(&ctx, fp);
|
|
||||||
fclose(fp);
|
|
||||||
|
|
||||||
if (!ok)
|
|
||||||
{
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ctx.num_words == 0)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "No words found in file\n");
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
vocab_assign_ranks(&ctx);
|
|
||||||
|
|
||||||
if (max_vocab_mode > 0)
|
|
||||||
{
|
|
||||||
int best_start = 0;
|
|
||||||
int best_length = 0;
|
|
||||||
vocab_find_longest_excerpt(&ctx, max_vocab_mode, &best_start, &best_length);
|
|
||||||
print_longest_excerpt_result(&ctx, max_vocab_mode, best_start, best_length);
|
|
||||||
|
|
||||||
if (dump_vocab)
|
|
||||||
{
|
|
||||||
if (dump_max_rank == 0)
|
|
||||||
dump_max_rank = max_vocab_mode;
|
|
||||||
dump_vocabulary(&ctx, dump_max_rank);
|
|
||||||
}
|
|
||||||
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ExcerptResult *results = malloc(max_length * sizeof(ExcerptResult));
|
|
||||||
if (!results)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Memory allocation failed\n");
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
vocab_find_optimal_excerpts(&ctx, max_length, results);
|
|
||||||
print_results(&ctx, results, max_length);
|
|
||||||
|
|
||||||
if (dump_vocab)
|
|
||||||
{
|
|
||||||
if (dump_max_rank == 0 && max_length > 0)
|
|
||||||
dump_max_rank = results[max_length - 1].min_vocab_needed;
|
|
||||||
if (dump_max_rank > 0)
|
|
||||||
dump_vocabulary(&ctx, dump_max_rank);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(results);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
make
|
|
||||||
./vocabulary_curve
|
|
||||||
@ -1,627 +0,0 @@
|
|||||||
/*
|
|
||||||
* test_vocabulary.c - Unit tests for vocabulary.c
|
|
||||||
*
|
|
||||||
* Tests cover all public functions declared in vocabulary.h using small
|
|
||||||
* in-memory inputs (no file I/O dependency outside vocab_process_stream).
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "vocabulary.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/* Helper: build a VocabContext from a literal string.
|
|
||||||
* Returns true on success. */
|
|
||||||
static bool ctx_from_string(VocabContext *ctx, const char *text)
|
|
||||||
{
|
|
||||||
vocab_init(ctx);
|
|
||||||
FILE *fp = fmemopen((void *)text, strlen(text), "r");
|
|
||||||
if (!fp)
|
|
||||||
return false;
|
|
||||||
bool ok = vocab_process_stream(ctx, fp);
|
|
||||||
fclose(fp);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* vocab_hash_word */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
static void test_hash_word_deterministic(void)
|
|
||||||
{
|
|
||||||
unsigned int h1 = vocab_hash_word("hello");
|
|
||||||
unsigned int h2 = vocab_hash_word("hello");
|
|
||||||
assert(h1 == h2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_hash_word_different(void)
|
|
||||||
{
|
|
||||||
unsigned int h1 = vocab_hash_word("apple");
|
|
||||||
unsigned int h2 = vocab_hash_word("orange");
|
|
||||||
/* Not guaranteed to differ in general, but these definitely do */
|
|
||||||
(void)h1;
|
|
||||||
(void)h2; /* no assertion — just ensure no crash */
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_hash_word_empty_string(void)
|
|
||||||
{
|
|
||||||
unsigned int h = vocab_hash_word("");
|
|
||||||
assert(h < HASH_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_hash_word_in_range(void)
|
|
||||||
{
|
|
||||||
unsigned int h = vocab_hash_word("test");
|
|
||||||
assert(h < HASH_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* vocab_is_word_char */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
static void test_is_word_char_alpha(void)
|
|
||||||
{
|
|
||||||
assert(vocab_is_word_char('a'));
|
|
||||||
assert(vocab_is_word_char('Z'));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_is_word_char_digit(void)
|
|
||||||
{
|
|
||||||
assert(vocab_is_word_char('0'));
|
|
||||||
assert(vocab_is_word_char('9'));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_is_word_char_underscore(void) { assert(vocab_is_word_char('_')); }
|
|
||||||
|
|
||||||
static void test_is_word_char_punctuation(void)
|
|
||||||
{
|
|
||||||
assert(!vocab_is_word_char(' '));
|
|
||||||
assert(!vocab_is_word_char('.'));
|
|
||||||
assert(!vocab_is_word_char(','));
|
|
||||||
assert(!vocab_is_word_char('\n'));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_is_word_char_high_byte(void)
|
|
||||||
{
|
|
||||||
/* Characters >= 128 (UTF-8 continuation bytes) are word characters */
|
|
||||||
assert(vocab_is_word_char(200));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* vocab_init / vocab_cleanup */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
static void test_init_zeroes_context(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
vocab_init(&ctx);
|
|
||||||
assert(ctx.num_unique_words == 0);
|
|
||||||
assert(ctx.num_words == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_cleanup_resets_counts(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
ctx_from_string(&ctx, "hello world hello");
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
assert(ctx.num_unique_words == 0);
|
|
||||||
assert(ctx.num_words == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* vocab_get_or_create_word */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
static void test_get_or_create_new_word(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
vocab_init(&ctx);
|
|
||||||
WordEntry *e = vocab_get_or_create_word(&ctx, "hello");
|
|
||||||
assert(e != NULL);
|
|
||||||
assert(strcmp(e->word, "hello") == 0);
|
|
||||||
assert(ctx.num_unique_words == 1);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_get_or_create_existing_word(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
vocab_init(&ctx);
|
|
||||||
WordEntry *e1 = vocab_get_or_create_word(&ctx, "hello");
|
|
||||||
WordEntry *e2 = vocab_get_or_create_word(&ctx, "hello");
|
|
||||||
assert(e1 == e2); /* Same pointer */
|
|
||||||
assert(ctx.num_unique_words == 1);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_get_or_create_multiple_words(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
vocab_init(&ctx);
|
|
||||||
vocab_get_or_create_word(&ctx, "apple");
|
|
||||||
vocab_get_or_create_word(&ctx, "banana");
|
|
||||||
vocab_get_or_create_word(&ctx, "cherry");
|
|
||||||
assert(ctx.num_unique_words == 3);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* vocab_process_stream */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
static void test_process_stream_basic(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
bool ok = ctx_from_string(&ctx, "the cat sat on the mat");
|
|
||||||
assert(ok);
|
|
||||||
assert(ctx.num_words == 6);
|
|
||||||
assert(ctx.num_unique_words == 5); /* "the" appears twice */
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_process_stream_empty_input(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
bool ok = ctx_from_string(&ctx, "");
|
|
||||||
assert(ok);
|
|
||||||
assert(ctx.num_words == 0);
|
|
||||||
assert(ctx.num_unique_words == 0);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_process_stream_single_word(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
bool ok = ctx_from_string(&ctx, "hello");
|
|
||||||
assert(ok);
|
|
||||||
assert(ctx.num_words == 1);
|
|
||||||
assert(ctx.num_unique_words == 1);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_process_stream_lowercases(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
ctx_from_string(&ctx, "Hello HELLO hello");
|
|
||||||
/* All three should map to the same "hello" entry */
|
|
||||||
assert(ctx.num_unique_words == 1);
|
|
||||||
assert(ctx.word_sequence[0]->count == 3);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_process_stream_last_word_no_trailing_space(void)
|
|
||||||
{
|
|
||||||
/* Last word has no trailing delimiter */
|
|
||||||
VocabContext ctx;
|
|
||||||
ctx_from_string(&ctx, "one two three");
|
|
||||||
assert(ctx.num_words == 3);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_process_stream_count_frequency(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
ctx_from_string(&ctx, "a a a b b c");
|
|
||||||
/* Find the entry for "a" */
|
|
||||||
WordEntry *entry_a = vocab_get_or_create_word(&ctx, "a");
|
|
||||||
assert(entry_a->count == 3);
|
|
||||||
WordEntry *entry_b = vocab_get_or_create_word(&ctx, "b");
|
|
||||||
assert(entry_b->count == 2);
|
|
||||||
WordEntry *entry_c = vocab_get_or_create_word(&ctx, "c");
|
|
||||||
assert(entry_c->count == 1);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Exercises hash chain traversal using two known-colliding words.
|
|
||||||
* word129 and word2200 both hash to slot 173186 (HASH_SIZE=200003). */
|
|
||||||
static void test_hash_chain_traversal(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
vocab_init(&ctx);
|
|
||||||
|
|
||||||
WordEntry *e1 = vocab_get_or_create_word(&ctx, "word129");
|
|
||||||
assert(e1 != NULL);
|
|
||||||
assert(ctx.num_unique_words == 1);
|
|
||||||
|
|
||||||
/* This collides with word129 -> exercises entry = entry->next */
|
|
||||||
WordEntry *e2 = vocab_get_or_create_word(&ctx, "word2200");
|
|
||||||
assert(e2 != NULL);
|
|
||||||
assert(e2 != e1);
|
|
||||||
assert(ctx.num_unique_words == 2);
|
|
||||||
|
|
||||||
/* Look up again - exercises chain traversal on find path */
|
|
||||||
WordEntry *e1b = vocab_get_or_create_word(&ctx, "word129");
|
|
||||||
assert(e1b == e1);
|
|
||||||
WordEntry *e2b = vocab_get_or_create_word(&ctx, "word2200");
|
|
||||||
assert(e2b == e2);
|
|
||||||
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Test that process_stream returns false when num_words is full */
|
|
||||||
static void test_process_stream_too_many_words(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
vocab_init(&ctx);
|
|
||||||
/* Pre-fill "one" entry so the word is known */
|
|
||||||
WordEntry *dummy = vocab_get_or_create_word(&ctx, "one");
|
|
||||||
assert(dummy != NULL);
|
|
||||||
/* Saturate num_words so the second word overflows */
|
|
||||||
ctx.num_words = MAX_WORDS;
|
|
||||||
/* "one" is already in hash - won't use get_or_create; second word "two" will.
|
|
||||||
* But actually process_stream checks num_words AFTER get_or_create, so we
|
|
||||||
* need the *first* NEW word to trigger overflow.
|
|
||||||
* Let's just pre-fill num_words to MAX_WORDS and start fresh with "two". */
|
|
||||||
ctx.num_words = MAX_WORDS;
|
|
||||||
|
|
||||||
FILE *fp = fmemopen((void *)"two", 3, "r");
|
|
||||||
assert(fp != NULL);
|
|
||||||
bool ok = vocab_process_stream(&ctx, fp);
|
|
||||||
fclose(fp);
|
|
||||||
/* "two" ends without whitespace - handled by last-word branch, which also
|
|
||||||
* checks num_words < MAX_WORDS before inserting (doesn't error).
|
|
||||||
* Re-check: the mid-stream path (line 182) fires on words with trailing
|
|
||||||
* whitespace when num_words >= MAX_WORDS after the get_or_create call. */
|
|
||||||
(void)ok;
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Cover line 182: return false in mid-stream loop when num_words >= MAX_WORDS */
|
|
||||||
static void test_process_stream_overflow_mid_stream(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
vocab_init(&ctx);
|
|
||||||
/* Pre-load all MAX_WORDS slots are "used" */
|
|
||||||
ctx.num_words = MAX_WORDS;
|
|
||||||
|
|
||||||
/* Provide "word " (with trailing space) so the loop path (not last-word) fires */
|
|
||||||
FILE *fp = fmemopen((void *)"alpha ", 6, "r");
|
|
||||||
assert(fp != NULL);
|
|
||||||
bool ok = vocab_process_stream(&ctx, fp);
|
|
||||||
fclose(fp);
|
|
||||||
assert(!ok);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Test get_or_create_word returns NULL when num_unique_words is exhausted */
|
|
||||||
static void test_get_or_create_returns_null_on_overflow(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
vocab_init(&ctx);
|
|
||||||
ctx.num_unique_words = MAX_UNIQUE_WORDS;
|
|
||||||
WordEntry *e = vocab_get_or_create_word(&ctx, "overflow");
|
|
||||||
assert(e == NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Test malloc failure path in get_or_create_word */
|
|
||||||
static void test_get_or_create_malloc_failure(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
vocab_init(&ctx);
|
|
||||||
vocab_test_fail_malloc_count = 1;
|
|
||||||
WordEntry *e = vocab_get_or_create_word(&ctx, "testword");
|
|
||||||
assert(e == NULL);
|
|
||||||
assert(vocab_test_fail_malloc_count == 0);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Cover line 182: process_stream returns false when get_or_create returns NULL */
|
|
||||||
static void test_process_stream_get_or_create_fails_mid(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
vocab_init(&ctx);
|
|
||||||
vocab_test_fail_malloc_count = 1;
|
|
||||||
FILE *fp = fmemopen((void *)"newword here", 12, "r");
|
|
||||||
assert(fp != NULL);
|
|
||||||
bool ok = vocab_process_stream(&ctx, fp);
|
|
||||||
fclose(fp);
|
|
||||||
assert(!ok);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Cover line 202: process_stream returns false when last-word get_or_create fails */
|
|
||||||
static void test_process_stream_get_or_create_fails_last_word(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
vocab_init(&ctx);
|
|
||||||
vocab_test_fail_malloc_count = 1;
|
|
||||||
/* No trailing space - goes to last-word branch */
|
|
||||||
FILE *fp = fmemopen((void *)"justoneword", 11, "r");
|
|
||||||
assert(fp != NULL);
|
|
||||||
bool ok = vocab_process_stream(&ctx, fp);
|
|
||||||
fclose(fp);
|
|
||||||
assert(!ok);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* vocab_compare_by_count */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
static void test_compare_by_count(void)
|
|
||||||
{
|
|
||||||
WordEntry a = {.count = 5};
|
|
||||||
WordEntry b = {.count = 3};
|
|
||||||
|
|
||||||
const WordEntry *pa = &a;
|
|
||||||
const WordEntry *pb = &b;
|
|
||||||
|
|
||||||
/* a(5) > b(3): compare should return negative (b - a = 3 - 5 = -2 < 0) */
|
|
||||||
int result = vocab_compare_by_count(&pa, &pb);
|
|
||||||
assert(result < 0); /* Descending: higher count should come first */
|
|
||||||
|
|
||||||
int result2 = vocab_compare_by_count(&pb, &pa);
|
|
||||||
assert(result2 > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_compare_by_count_equal(void)
|
|
||||||
{
|
|
||||||
WordEntry a = {.count = 4};
|
|
||||||
WordEntry b = {.count = 4};
|
|
||||||
|
|
||||||
const WordEntry *pa = &a;
|
|
||||||
const WordEntry *pb = &b;
|
|
||||||
|
|
||||||
assert(vocab_compare_by_count(&pa, &pb) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* vocab_assign_ranks */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
static void test_assign_ranks_basic(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
/* "the" x3, "cat" x2, "sat" x1 */
|
|
||||||
ctx_from_string(&ctx, "the the the cat cat sat");
|
|
||||||
vocab_assign_ranks(&ctx);
|
|
||||||
|
|
||||||
WordEntry *the_entry = vocab_get_or_create_word(&ctx, "the");
|
|
||||||
WordEntry *cat_entry = vocab_get_or_create_word(&ctx, "cat");
|
|
||||||
WordEntry *sat_entry = vocab_get_or_create_word(&ctx, "sat");
|
|
||||||
|
|
||||||
assert(the_entry->rank == 1);
|
|
||||||
assert(cat_entry->rank == 2);
|
|
||||||
assert(sat_entry->rank == 3);
|
|
||||||
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_assign_ranks_tied(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
/* "a" x2, "b" x2, "c" x1 */
|
|
||||||
ctx_from_string(&ctx, "a a b b c");
|
|
||||||
vocab_assign_ranks(&ctx);
|
|
||||||
|
|
||||||
WordEntry *a_entry = vocab_get_or_create_word(&ctx, "a");
|
|
||||||
WordEntry *b_entry = vocab_get_or_create_word(&ctx, "b");
|
|
||||||
WordEntry *c_entry = vocab_get_or_create_word(&ctx, "c");
|
|
||||||
|
|
||||||
/* a and b both rank 1; c gets rank 3 (competition ranking) */
|
|
||||||
assert(a_entry->rank == 1);
|
|
||||||
assert(b_entry->rank == 1);
|
|
||||||
assert(c_entry->rank == 3);
|
|
||||||
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* vocab_analyze_excerpt */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
static void test_analyze_excerpt_single_word(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
ctx_from_string(&ctx, "apple banana cherry");
|
|
||||||
vocab_assign_ranks(&ctx);
|
|
||||||
|
|
||||||
int max_rank = vocab_analyze_excerpt(&ctx, 0, 1);
|
|
||||||
assert(max_rank == 1); /* All-unique: first word gets rank 1 */
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_analyze_excerpt_repeated_word(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
/* "the" is most common (rank 1) */
|
|
||||||
ctx_from_string(&ctx, "the cat the dog the");
|
|
||||||
vocab_assign_ranks(&ctx);
|
|
||||||
|
|
||||||
/* Excerpt "the the": only uses rank-1 word */
|
|
||||||
int max_rank = vocab_analyze_excerpt(&ctx, 0, 1);
|
|
||||||
assert(max_rank == 1);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_analyze_excerpt_full_text(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
/* Make each word appear a unique number of times so ranks 1..4 are assigned */
|
|
||||||
ctx_from_string(&ctx, "a a a a b b b c c d");
|
|
||||||
vocab_assign_ranks(&ctx);
|
|
||||||
|
|
||||||
/* Full 10-word excerpt: needs rank 4 (word "d" appears once, rank 4) */
|
|
||||||
int max_rank = vocab_analyze_excerpt(&ctx, 0, 10);
|
|
||||||
assert(max_rank == 4);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* vocab_find_optimal_excerpts */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
static void test_find_optimal_excerpts_length1(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
/* "the" most frequent (rank 1); best 1-word excerpt uses only rank-1 word */
|
|
||||||
ctx_from_string(&ctx, "the the the cat dog");
|
|
||||||
vocab_assign_ranks(&ctx);
|
|
||||||
|
|
||||||
ExcerptResult results[1];
|
|
||||||
vocab_find_optimal_excerpts(&ctx, 1, results);
|
|
||||||
|
|
||||||
assert(results[0].excerpt_length == 1);
|
|
||||||
assert(results[0].min_vocab_needed == 1); /* Best excerpt is "the" */
|
|
||||||
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_find_optimal_excerpts_monotone(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
ctx_from_string(&ctx, "the cat sat on the mat");
|
|
||||||
vocab_assign_ranks(&ctx);
|
|
||||||
|
|
||||||
int max_length = 4;
|
|
||||||
ExcerptResult results[4];
|
|
||||||
vocab_find_optimal_excerpts(&ctx, max_length, results);
|
|
||||||
|
|
||||||
/* Vocab needed should be >= previous (weakly monotone) */
|
|
||||||
for (int i = 1; i < max_length; i++)
|
|
||||||
{
|
|
||||||
assert(results[i].min_vocab_needed >= results[i - 1].min_vocab_needed);
|
|
||||||
}
|
|
||||||
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* vocab_find_longest_excerpt */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
static void test_find_longest_excerpt_unlimited(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
ctx_from_string(&ctx, "the cat sat on the mat");
|
|
||||||
vocab_assign_ranks(&ctx);
|
|
||||||
|
|
||||||
int start = 0;
|
|
||||||
int length = 0;
|
|
||||||
/* All 5 unique words have ranks 1..5; max_vocab >= 5 means all qualify */
|
|
||||||
vocab_find_longest_excerpt(&ctx, 5, &start, &length);
|
|
||||||
assert(length == 6); /* Entire text */
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_find_longest_excerpt_restrictive(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
/* "rare" has rank 5; with max_vocab=1 it can't appear */
|
|
||||||
ctx_from_string(&ctx, "the the the rare the the");
|
|
||||||
vocab_assign_ranks(&ctx);
|
|
||||||
/* "the" rank 1, "rare" rank 2 */
|
|
||||||
|
|
||||||
int start = 0;
|
|
||||||
int length = 0;
|
|
||||||
vocab_find_longest_excerpt(&ctx, 1, &start, &length);
|
|
||||||
/* Best run is "the the the" (3 words) before "rare" */
|
|
||||||
assert(length == 3);
|
|
||||||
assert(start == 0);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_find_longest_excerpt_no_valid(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
ctx_from_string(&ctx, "rare word here");
|
|
||||||
vocab_assign_ranks(&ctx);
|
|
||||||
/* All words rank >= 1; with max_vocab=0 nothing can qualify */
|
|
||||||
|
|
||||||
int start = 0;
|
|
||||||
int length = 0;
|
|
||||||
vocab_find_longest_excerpt(&ctx, 0, &start, &length);
|
|
||||||
assert(length == 0);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_find_longest_excerpt_mid_sequence(void)
|
|
||||||
{
|
|
||||||
VocabContext ctx;
|
|
||||||
/* "rare" appears twice (rank 1 due to count=2),
|
|
||||||
* "odd" appears once (rank 2)
|
|
||||||
* sequence: odd rare rare rare odd
|
|
||||||
* With max_vocab=1 (only "rare"):
|
|
||||||
* window spans positions 1,2,3 -> length 3 */
|
|
||||||
ctx_from_string(&ctx, "odd rare rare rare odd");
|
|
||||||
vocab_assign_ranks(&ctx);
|
|
||||||
/* "rare" has count 3 -> rank 1; "odd" has count 2 -> rank 2 */
|
|
||||||
|
|
||||||
int start = 0;
|
|
||||||
int length = 0;
|
|
||||||
vocab_find_longest_excerpt(&ctx, 1, &start, &length);
|
|
||||||
assert(length == 3);
|
|
||||||
assert(start == 1);
|
|
||||||
vocab_cleanup(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* Main */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
/* vocab_hash_word */
|
|
||||||
test_hash_word_deterministic();
|
|
||||||
test_hash_word_different();
|
|
||||||
test_hash_word_empty_string();
|
|
||||||
test_hash_word_in_range();
|
|
||||||
|
|
||||||
/* vocab_is_word_char */
|
|
||||||
test_is_word_char_alpha();
|
|
||||||
test_is_word_char_digit();
|
|
||||||
test_is_word_char_underscore();
|
|
||||||
test_is_word_char_punctuation();
|
|
||||||
test_is_word_char_high_byte();
|
|
||||||
|
|
||||||
/* vocab_init / vocab_cleanup */
|
|
||||||
test_init_zeroes_context();
|
|
||||||
test_cleanup_resets_counts();
|
|
||||||
|
|
||||||
/* vocab_get_or_create_word */
|
|
||||||
test_get_or_create_new_word();
|
|
||||||
test_get_or_create_existing_word();
|
|
||||||
test_get_or_create_multiple_words();
|
|
||||||
test_get_or_create_returns_null_on_overflow();
|
|
||||||
test_get_or_create_malloc_failure();
|
|
||||||
|
|
||||||
/* vocab_process_stream */
|
|
||||||
test_process_stream_basic();
|
|
||||||
test_process_stream_empty_input();
|
|
||||||
test_process_stream_single_word();
|
|
||||||
test_process_stream_lowercases();
|
|
||||||
test_process_stream_last_word_no_trailing_space();
|
|
||||||
test_process_stream_count_frequency();
|
|
||||||
test_hash_chain_traversal();
|
|
||||||
test_process_stream_too_many_words();
|
|
||||||
test_process_stream_overflow_mid_stream();
|
|
||||||
test_process_stream_get_or_create_fails_mid();
|
|
||||||
test_process_stream_get_or_create_fails_last_word();
|
|
||||||
|
|
||||||
/* vocab_compare_by_count */
|
|
||||||
test_compare_by_count();
|
|
||||||
test_compare_by_count_equal();
|
|
||||||
|
|
||||||
/* vocab_assign_ranks */
|
|
||||||
test_assign_ranks_basic();
|
|
||||||
test_assign_ranks_tied();
|
|
||||||
|
|
||||||
/* vocab_analyze_excerpt */
|
|
||||||
test_analyze_excerpt_single_word();
|
|
||||||
test_analyze_excerpt_repeated_word();
|
|
||||||
test_analyze_excerpt_full_text();
|
|
||||||
|
|
||||||
/* vocab_find_optimal_excerpts */
|
|
||||||
test_find_optimal_excerpts_length1();
|
|
||||||
test_find_optimal_excerpts_monotone();
|
|
||||||
|
|
||||||
/* vocab_find_longest_excerpt */
|
|
||||||
test_find_longest_excerpt_unlimited();
|
|
||||||
test_find_longest_excerpt_restrictive();
|
|
||||||
test_find_longest_excerpt_no_valid();
|
|
||||||
test_find_longest_excerpt_mid_sequence();
|
|
||||||
|
|
||||||
printf("All tests passed (%d tests).\n", 40);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@ -1,281 +0,0 @@
|
|||||||
/*
|
|
||||||
* vocabulary.c - Core vocabulary analysis logic.
|
|
||||||
*/
|
|
||||||
#include "vocabulary.h"
|
|
||||||
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/* Test hook: test code can set this to make the next N malloc calls fail */
|
|
||||||
int vocab_test_fail_malloc_count = 0;
|
|
||||||
|
|
||||||
static void *vocab_malloc(size_t size)
|
|
||||||
{
|
|
||||||
if (vocab_test_fail_malloc_count > 0)
|
|
||||||
{
|
|
||||||
vocab_test_fail_malloc_count--;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return malloc(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* Initialise / cleanup */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
void vocab_init(VocabContext *ctx)
|
|
||||||
{
|
|
||||||
memset(ctx->hash_table, 0, sizeof(ctx->hash_table));
|
|
||||||
ctx->num_unique_words = 0;
|
|
||||||
ctx->num_words = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void vocab_cleanup(VocabContext *ctx)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < ctx->num_unique_words; i++)
|
|
||||||
{
|
|
||||||
free(ctx->all_entries[i]);
|
|
||||||
}
|
|
||||||
ctx->num_unique_words = 0;
|
|
||||||
ctx->num_words = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* Hash table helpers */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
unsigned int vocab_hash_word(const char *word)
|
|
||||||
{
|
|
||||||
unsigned int hash = 5381;
|
|
||||||
int c;
|
|
||||||
while ((c = *word++))
|
|
||||||
{
|
|
||||||
hash = ((hash << 5) + hash) + (unsigned int)c;
|
|
||||||
}
|
|
||||||
return hash % HASH_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
WordEntry *vocab_get_or_create_word(VocabContext *ctx, const char *word)
|
|
||||||
{
|
|
||||||
unsigned int h = vocab_hash_word(word);
|
|
||||||
WordEntry *entry = ctx->hash_table[h];
|
|
||||||
|
|
||||||
while (entry)
|
|
||||||
{
|
|
||||||
if (strcmp(entry->word, word) == 0)
|
|
||||||
{
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
entry = entry->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create new entry */
|
|
||||||
if (ctx->num_unique_words >= MAX_UNIQUE_WORDS)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Too many unique words\n");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
entry = vocab_malloc(sizeof(WordEntry));
|
|
||||||
if (!entry)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Memory allocation failed\n");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
strncpy(entry->word, word, MAX_WORD_LEN - 1);
|
|
||||||
entry->word[MAX_WORD_LEN - 1] = '\0';
|
|
||||||
entry->count = 0;
|
|
||||||
entry->rank = 0;
|
|
||||||
entry->next = ctx->hash_table[h];
|
|
||||||
ctx->hash_table[h] = entry;
|
|
||||||
|
|
||||||
ctx->all_entries[ctx->num_unique_words++] = entry;
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* Character classification */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
bool vocab_is_word_char(int c) { return isalnum(c) || c == '_' || (unsigned char)c >= 128; }
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* Sorting / ranking */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
int vocab_compare_by_count(const void *a, const void *b)
|
|
||||||
{
|
|
||||||
const WordEntry *wa = *(const WordEntry **)a;
|
|
||||||
const WordEntry *wb = *(const WordEntry **)b;
|
|
||||||
return wb->count - wa->count; /* Descending */
|
|
||||||
}
|
|
||||||
|
|
||||||
void vocab_assign_ranks(VocabContext *ctx)
|
|
||||||
{
|
|
||||||
qsort(ctx->all_entries, ctx->num_unique_words, sizeof(WordEntry *), vocab_compare_by_count);
|
|
||||||
|
|
||||||
for (int i = 0; i < ctx->num_unique_words; i++)
|
|
||||||
{
|
|
||||||
if (i == 0)
|
|
||||||
{
|
|
||||||
ctx->all_entries[i]->rank = 1;
|
|
||||||
}
|
|
||||||
else if (ctx->all_entries[i]->count == ctx->all_entries[i - 1]->count)
|
|
||||||
{
|
|
||||||
ctx->all_entries[i]->rank = ctx->all_entries[i - 1]->rank;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ctx->all_entries[i]->rank = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* Sliding-window analysis */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
int vocab_analyze_excerpt(const VocabContext *ctx, int start, int length)
|
|
||||||
{
|
|
||||||
static bool seen_rank[MAX_UNIQUE_WORDS + 1];
|
|
||||||
memset(seen_rank, 0, (ctx->num_unique_words + 1) * sizeof(bool));
|
|
||||||
|
|
||||||
int max_rank = 0;
|
|
||||||
|
|
||||||
for (int i = start; i < start + length; i++)
|
|
||||||
{
|
|
||||||
WordEntry *entry = ctx->word_sequence[i];
|
|
||||||
int rank = entry->rank;
|
|
||||||
|
|
||||||
if (!seen_rank[rank])
|
|
||||||
{
|
|
||||||
seen_rank[rank] = true;
|
|
||||||
if (rank > max_rank)
|
|
||||||
{
|
|
||||||
max_rank = rank;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return max_rank;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* File I/O */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
bool vocab_process_stream(VocabContext *ctx, FILE *fp)
|
|
||||||
{
|
|
||||||
char word[MAX_WORD_LEN];
|
|
||||||
int word_len = 0;
|
|
||||||
int c;
|
|
||||||
|
|
||||||
while ((c = fgetc(fp)) != EOF)
|
|
||||||
{
|
|
||||||
if (vocab_is_word_char(c))
|
|
||||||
{
|
|
||||||
if (word_len < MAX_WORD_LEN - 1)
|
|
||||||
{
|
|
||||||
word[word_len++] = tolower(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (word_len > 0)
|
|
||||||
{
|
|
||||||
word[word_len] = '\0';
|
|
||||||
|
|
||||||
WordEntry *entry = vocab_get_or_create_word(ctx, word);
|
|
||||||
if (!entry)
|
|
||||||
return false;
|
|
||||||
entry->count++;
|
|
||||||
|
|
||||||
if (ctx->num_words >= MAX_WORDS)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Too many words in file\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx->word_sequence[ctx->num_words++] = entry;
|
|
||||||
word_len = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Handle last word if file doesn't end with whitespace */
|
|
||||||
if (word_len > 0)
|
|
||||||
{
|
|
||||||
word[word_len] = '\0';
|
|
||||||
WordEntry *entry = vocab_get_or_create_word(ctx, word);
|
|
||||||
if (!entry)
|
|
||||||
return false;
|
|
||||||
entry->count++;
|
|
||||||
|
|
||||||
if (ctx->num_words < MAX_WORDS)
|
|
||||||
{
|
|
||||||
ctx->word_sequence[ctx->num_words++] = entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* Optimal-excerpt search */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
void vocab_find_optimal_excerpts(const VocabContext *ctx, int max_length, ExcerptResult *results)
|
|
||||||
{
|
|
||||||
for (int length = 1; length <= max_length && length <= ctx->num_words; length++)
|
|
||||||
{
|
|
||||||
int best_vocab = ctx->num_unique_words + 1;
|
|
||||||
int best_start = 0;
|
|
||||||
|
|
||||||
for (int start = 0; start <= ctx->num_words - length; start++)
|
|
||||||
{
|
|
||||||
int vocab_needed = vocab_analyze_excerpt(ctx, start, length);
|
|
||||||
|
|
||||||
if (vocab_needed < best_vocab)
|
|
||||||
{
|
|
||||||
best_vocab = vocab_needed;
|
|
||||||
best_start = start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
results[length - 1].excerpt_length = length;
|
|
||||||
results[length - 1].min_vocab_needed = best_vocab;
|
|
||||||
results[length - 1].start_pos = best_start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* Inverse mode */
|
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
void vocab_find_longest_excerpt(const VocabContext *ctx, int max_vocab, int *out_start,
|
|
||||||
int *out_length)
|
|
||||||
{
|
|
||||||
int best_start = 0;
|
|
||||||
int best_length = 0;
|
|
||||||
|
|
||||||
int left = 0;
|
|
||||||
for (int right = 0; right < ctx->num_words; right++)
|
|
||||||
{
|
|
||||||
if (ctx->word_sequence[right]->rank > max_vocab)
|
|
||||||
{
|
|
||||||
left = right + 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int length = right - left + 1;
|
|
||||||
if (length > best_length)
|
|
||||||
{
|
|
||||||
best_length = length;
|
|
||||||
best_start = left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*out_start = best_start;
|
|
||||||
*out_length = best_length;
|
|
||||||
}
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
/*
|
|
||||||
* vocabulary.h - Core vocabulary analysis logic, extracted for testability.
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#define MAX_WORD_LEN 64
|
|
||||||
#define MAX_WORDS 500000
|
|
||||||
#define MAX_UNIQUE_WORDS 100000
|
|
||||||
#define HASH_SIZE 200003 /* Prime number for better distribution */
|
|
||||||
|
|
||||||
/* Word entry for hash table */
|
|
||||||
typedef struct WordEntry
|
|
||||||
{
|
|
||||||
char word[MAX_WORD_LEN];
|
|
||||||
int count;
|
|
||||||
int rank; /* 1-indexed rank by frequency (1 = most common) */
|
|
||||||
struct WordEntry *next;
|
|
||||||
} WordEntry;
|
|
||||||
|
|
||||||
/* Result for each excerpt length */
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
int excerpt_length;
|
|
||||||
int min_vocab_needed;
|
|
||||||
int start_pos; /* Start position in word_sequence */
|
|
||||||
} ExcerptResult;
|
|
||||||
|
|
||||||
/* Context holding all mutable state (replaces static globals) */
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
WordEntry *hash_table[HASH_SIZE];
|
|
||||||
WordEntry *all_entries[MAX_UNIQUE_WORDS];
|
|
||||||
int num_unique_words;
|
|
||||||
WordEntry *word_sequence[MAX_WORDS];
|
|
||||||
int num_words;
|
|
||||||
} VocabContext;
|
|
||||||
|
|
||||||
/* Initialise a fresh context (zero everything) */
|
|
||||||
void vocab_init(VocabContext *ctx);
|
|
||||||
|
|
||||||
/* Free all allocated WordEntry nodes inside ctx */
|
|
||||||
void vocab_cleanup(VocabContext *ctx);
|
|
||||||
|
|
||||||
/* Hash a word (public for tests) */
|
|
||||||
unsigned int vocab_hash_word(const char *word);
|
|
||||||
|
|
||||||
/* Find or create a word entry in the context */
|
|
||||||
WordEntry *vocab_get_or_create_word(VocabContext *ctx, const char *word);
|
|
||||||
|
|
||||||
/* Check if a character can be part of a word */
|
|
||||||
bool vocab_is_word_char(int c);
|
|
||||||
|
|
||||||
/* Comparator for qsort (descending count) */
|
|
||||||
int vocab_compare_by_count(const void *a, const void *b);
|
|
||||||
|
|
||||||
/* Assign frequency ranks to all entries in ctx */
|
|
||||||
void vocab_assign_ranks(VocabContext *ctx);
|
|
||||||
|
|
||||||
/* Analyse one excerpt window and return the max rank required */
|
|
||||||
int vocab_analyze_excerpt(const VocabContext *ctx, int start, int length);
|
|
||||||
|
|
||||||
/* Read and index words from an open FILE stream into ctx */
|
|
||||||
bool vocab_process_stream(VocabContext *ctx, FILE *fp);
|
|
||||||
|
|
||||||
/* Find optimal excerpts for lengths 1..max_length; results[] must be
|
|
||||||
* pre-allocated to max_length elements */
|
|
||||||
void vocab_find_optimal_excerpts(const VocabContext *ctx, int max_length, ExcerptResult *results);
|
|
||||||
|
|
||||||
/* Inverse mode: find longest contiguous excerpt using only top-N vocab */
|
|
||||||
void vocab_find_longest_excerpt(const VocabContext *ctx, int max_vocab, int *out_start,
|
|
||||||
int *out_length);
|
|
||||||
|
|
||||||
/* Test hook: set to non-zero to make the next malloc call(s) return NULL.
|
|
||||||
* Only used by test_vocabulary.c to exercise the malloc-failure path. */
|
|
||||||
extern int vocab_test_fail_malloc_count;
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
CC := gcc
|
|
||||||
CFLAGS := -O2 -Wall -Wextra -std=c11 $(shell pkg-config --cflags libwebsockets 2>/dev/null)
|
|
||||||
LDFLAGS := $(shell pkg-config --libs libwebsockets 2>/dev/null)
|
|
||||||
|
|
||||||
SRC := main.c
|
|
||||||
BIN := websocket_server
|
|
||||||
|
|
||||||
all: $(BIN)
|
|
||||||
|
|
||||||
$(BIN): $(SRC)
|
|
||||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
run: $(BIN)
|
|
||||||
./$(BIN)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(BIN)
|
|
||||||
|
|
||||||
.PHONY: all run clean
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
#include <libwebsockets.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
static int interrupted;
|
|
||||||
static struct lws_context *context;
|
|
||||||
|
|
||||||
// Callback for WebSocket communication
|
|
||||||
static int callback_function(struct lws *wsi, enum lws_callback_reasons reason, void *user,
|
|
||||||
void *in, size_t len)
|
|
||||||
{
|
|
||||||
switch (reason)
|
|
||||||
{
|
|
||||||
case LWS_CALLBACK_CLIENT_ESTABLISHED:
|
|
||||||
printf("WebSocket connection established\n");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LWS_CALLBACK_CLIENT_RECEIVE:
|
|
||||||
printf("Received data: %s\n", (char *)in);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LWS_CALLBACK_CLIENT_WRITEABLE:
|
|
||||||
{
|
|
||||||
const char *msg = "Hello, WebSocket server!";
|
|
||||||
unsigned char buf[LWS_PRE + 512];
|
|
||||||
size_t msg_len = strlen(msg);
|
|
||||||
memcpy(&buf[LWS_PRE], msg, msg_len);
|
|
||||||
lws_write(wsi, &buf[LWS_PRE], msg_len, LWS_WRITE_TEXT);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case LWS_CALLBACK_CLOSED:
|
|
||||||
printf("WebSocket connection closed\n");
|
|
||||||
interrupted = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signal handler for clean exit
|
|
||||||
static void sigint_handler(int sig)
|
|
||||||
{
|
|
||||||
interrupted = 1;
|
|
||||||
lws_cancel_service(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
struct lws_protocols protocols[] = {
|
|
||||||
{
|
|
||||||
"ws-protocol", // Protocol name
|
|
||||||
callback_function, // Callback function
|
|
||||||
0, // Per-session data size
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{NULL, NULL, 0, 0} // End of list
|
|
||||||
};
|
|
||||||
|
|
||||||
struct lws_client_connect_info ccinfo = {0};
|
|
||||||
struct lws_context_creation_info info = {0};
|
|
||||||
info.port = CONTEXT_PORT_NO_LISTEN;
|
|
||||||
info.protocols = protocols;
|
|
||||||
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
|
|
||||||
|
|
||||||
// Create WebSocket context
|
|
||||||
context = lws_create_context(&info);
|
|
||||||
if (!context)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Failed to create context\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
signal(SIGINT, sigint_handler);
|
|
||||||
|
|
||||||
// Configure connection details
|
|
||||||
ccinfo.context = context;
|
|
||||||
ccinfo.address = "echo.websocket.org"; // WebSocket server address
|
|
||||||
ccinfo.port = 443; // Port (for WSS use 443)
|
|
||||||
ccinfo.path = "/"; // Path on the server
|
|
||||||
ccinfo.host = lws_canonical_hostname(context);
|
|
||||||
ccinfo.origin = "origin";
|
|
||||||
ccinfo.protocol = protocols[0].name;
|
|
||||||
ccinfo.ssl_connection = LCCSCF_USE_SSL; // Use SSL for secure WebSocket
|
|
||||||
|
|
||||||
// Initiate the WebSocket connection
|
|
||||||
struct lws *wsi = lws_client_connect_via_info(&ccinfo);
|
|
||||||
if (!wsi)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Failed to initiate WebSocket connection\n");
|
|
||||||
lws_context_destroy(context);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event loop
|
|
||||||
while (!interrupted)
|
|
||||||
{
|
|
||||||
lws_service(context, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
lws_context_destroy(context);
|
|
||||||
printf("Exiting...\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
# Install dependencies
|
|
||||||
if command -v pacman &>/dev/null; then
|
|
||||||
pacman -Q libwebsockets &>/dev/null || sudo pacman -S --noconfirm libwebsockets
|
|
||||||
elif command -v apt-get &>/dev/null; then
|
|
||||||
sudo apt-get install -y libwebsockets-dev
|
|
||||||
elif command -v dnf &>/dev/null; then
|
|
||||||
sudo dnf install -y libwebsockets-devel
|
|
||||||
elif command -v zypper &>/dev/null; then
|
|
||||||
sudo zypper install -y libwebsockets-devel
|
|
||||||
else
|
|
||||||
echo "Could not detect package manager. Please install libwebsockets manually." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
make
|
|
||||||
./websocket_server
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
CXX := g++
|
|
||||||
CXXFLAGS := -O2 -Wall -Wextra -std=c++17
|
|
||||||
LDFLAGS :=
|
|
||||||
|
|
||||||
BINS := howOftenDoesCharOccur quickchallenges reverseString solveQuadraticEquation
|
|
||||||
|
|
||||||
all: $(BINS)
|
|
||||||
|
|
||||||
howOftenDoesCharOccur: howOftenDoesCharOccur.cpp
|
|
||||||
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
quickchallenges: quickchallenges.cpp
|
|
||||||
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
reverseString: reverseString.cpp
|
|
||||||
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
solveQuadraticEquation: solveQuadraticEquation.cpp
|
|
||||||
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
# ---- Coverage build: separate compilation so gcov data is per source file ----
|
|
||||||
COV := -O2 -g --coverage -Wall -Wextra -std=c++17 -DTESTING
|
|
||||||
|
|
||||||
howOftenDoesCharOccur.o: howOftenDoesCharOccur.cpp
|
|
||||||
$(CXX) $(COV) -c -o $@ $<
|
|
||||||
|
|
||||||
quickchallenges.o: quickchallenges.cpp
|
|
||||||
$(CXX) $(COV) -c -o $@ $<
|
|
||||||
|
|
||||||
reverseString.o: reverseString.cpp
|
|
||||||
$(CXX) $(COV) -c -o $@ $<
|
|
||||||
|
|
||||||
solveQuadraticEquation.o: solveQuadraticEquation.cpp
|
|
||||||
$(CXX) $(COV) -c -o $@ $<
|
|
||||||
|
|
||||||
test_challenges.o: test_challenges.cpp
|
|
||||||
$(CXX) $(COV) -c -o $@ $<
|
|
||||||
|
|
||||||
TEST_OBJS := test_challenges.o howOftenDoesCharOccur.o quickchallenges.o reverseString.o solveQuadraticEquation.o
|
|
||||||
|
|
||||||
test_challenges: $(TEST_OBJS)
|
|
||||||
$(CXX) -O2 -g --coverage -o $@ $^
|
|
||||||
|
|
||||||
test: test_challenges
|
|
||||||
./test_challenges
|
|
||||||
|
|
||||||
coverage: test_challenges
|
|
||||||
./test_challenges
|
|
||||||
lcov --capture --directory . --output-file coverage.info \
|
|
||||||
--rc branch_coverage=1 --ignore-errors inconsistent,mismatch,unused
|
|
||||||
lcov --remove coverage.info '/usr/*' --output-file coverage.info \
|
|
||||||
--rc branch_coverage=1 --ignore-errors unused,inconsistent
|
|
||||||
lcov --extract coverage.info \
|
|
||||||
"$(CURDIR)/howOftenDoesCharOccur.cpp" \
|
|
||||||
"$(CURDIR)/quickchallenges.cpp" \
|
|
||||||
"$(CURDIR)/reverseString.cpp" \
|
|
||||||
"$(CURDIR)/solveQuadraticEquation.cpp" \
|
|
||||||
--output-file coverage.info \
|
|
||||||
--ignore-errors unused,inconsistent
|
|
||||||
@echo "--- Coverage Summary ---"
|
|
||||||
lcov --summary coverage.info --rc branch_coverage=1 2>&1 | tee /tmp/lcov_summary.txt
|
|
||||||
@LINE_COV=$$(grep "lines" /tmp/lcov_summary.txt | grep -oP '[0-9]+\.[0-9]+(?=%)'); \
|
|
||||||
echo "Line coverage: $${LINE_COV}%"; \
|
|
||||||
if [ "$$(echo "$${LINE_COV} < 100.0" | bc)" = "1" ]; then \
|
|
||||||
echo "FAIL: line coverage below 100%"; exit 1; \
|
|
||||||
fi
|
|
||||||
@echo "OK: 100% line coverage achieved"
|
|
||||||
|
|
||||||
run: all
|
|
||||||
./howOftenDoesCharOccur
|
|
||||||
./quickchallenges
|
|
||||||
./reverseString
|
|
||||||
./solveQuadraticEquation
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(BINS) test_challenges *.gcda *.gcno *.gcov coverage.info *.o
|
|
||||||
|
|
||||||
.PHONY: all run clean test coverage
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
CXX := g++
|
|
||||||
CXXFLAGS := -O2 -Wall -Wextra -std=c++17
|
|
||||||
LDFLAGS := -lm
|
|
||||||
|
|
||||||
SRC := main.cpp
|
|
||||||
BIN := pi
|
|
||||||
|
|
||||||
all: $(BIN)
|
|
||||||
|
|
||||||
$(BIN): $(SRC)
|
|
||||||
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
run: $(BIN)
|
|
||||||
./$(BIN)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(BIN)
|
|
||||||
|
|
||||||
.PHONY: all run clean
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
#include <iomanip>
|
|
||||||
#include <iostream>
|
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
make
|
|
||||||
./pi
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
CXX := g++
|
|
||||||
CXXFLAGS := -O2 -Wall -Wextra -std=c++17
|
|
||||||
LDFLAGS :=
|
|
||||||
|
|
||||||
SRC := brydz.cpp
|
|
||||||
BIN := brydz
|
|
||||||
|
|
||||||
all: $(BIN)
|
|
||||||
|
|
||||||
$(BIN): $(SRC)
|
|
||||||
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
run: $(BIN)
|
|
||||||
./$(BIN)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(BIN)
|
|
||||||
|
|
||||||
.PHONY: all run clean
|
|
||||||
@ -1,391 +0,0 @@
|
|||||||
#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 = {"Gracz A", "Gracz B"};
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
make
|
|
||||||
./brydz
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
CXX := g++
|
|
||||||
CXXFLAGS := -O2 -Wall -Wextra -std=c++17
|
|
||||||
LDFLAGS :=
|
|
||||||
|
|
||||||
# main.cpp includes basic.cpp directly
|
|
||||||
SRC := main.cpp
|
|
||||||
BIN := darts
|
|
||||||
|
|
||||||
all: $(BIN)
|
|
||||||
|
|
||||||
$(BIN): $(SRC)
|
|
||||||
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
run: $(BIN)
|
|
||||||
./$(BIN)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(BIN)
|
|
||||||
|
|
||||||
.PHONY: all run clean
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
#ifndef BASIC_CPP
|
|
||||||
#define BASIC_CPP
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
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
|
|
||||||
@ -1,150 +0,0 @@
|
|||||||
#ifndef MAIN_CPP
|
|
||||||
#define MAIN_CPP
|
|
||||||
|
|
||||||
#include "basic.cpp"
|
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Darts checkout finder: find combinations of up to 3 darts that reduce
|
|
||||||
// pointsLeft to exactly 0, finishing on a double (standard 501 rules).
|
|
||||||
std::vector<int> requiredShoots(const int pointsLeft) {
|
|
||||||
// All valid dart scores with labels
|
|
||||||
std::vector<std::pair<int, std::string>> all_darts;
|
|
||||||
for (int i = MIN_SPOT; i <= MAX_SPOT; i++)
|
|
||||||
all_darts.push_back({i, std::to_string(i)});
|
|
||||||
all_darts.push_back({25, "Bull"});
|
|
||||||
for (int i = MIN_SPOT; i <= MAX_SPOT; i++)
|
|
||||||
all_darts.push_back({i * 2, "D" + std::to_string(i)});
|
|
||||||
all_darts.push_back({50, "D-Bull"});
|
|
||||||
for (int i = MIN_SPOT; i <= MAX_SPOT; i++)
|
|
||||||
all_darts.push_back({i * 3, "T" + std::to_string(i)});
|
|
||||||
|
|
||||||
// Doubles only (valid finishing darts)
|
|
||||||
std::vector<std::pair<int, std::string>> doubles;
|
|
||||||
for (int i = MIN_SPOT; i <= MAX_SPOT; i++)
|
|
||||||
doubles.push_back({i * 2, "D" + std::to_string(i)});
|
|
||||||
doubles.push_back({50, "D-Bull"});
|
|
||||||
|
|
||||||
std::vector<std::vector<std::string>> checkouts;
|
|
||||||
const int MAX_RESULTS = 5;
|
|
||||||
|
|
||||||
// 1-dart checkouts
|
|
||||||
for (auto &d : doubles)
|
|
||||||
if (d.first == pointsLeft)
|
|
||||||
checkouts.push_back({d.second});
|
|
||||||
|
|
||||||
// 2-dart checkouts
|
|
||||||
for (auto &d1 : all_darts) {
|
|
||||||
for (auto &d2 : doubles) {
|
|
||||||
if (d1.first + d2.first == pointsLeft) {
|
|
||||||
checkouts.push_back({d1.second, d2.second});
|
|
||||||
if ((int)checkouts.size() >= MAX_RESULTS)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3-dart checkouts (stop early once we have enough results)
|
|
||||||
for (auto &d1 : all_darts) {
|
|
||||||
for (auto &d2 : all_darts) {
|
|
||||||
for (auto &d3 : doubles) {
|
|
||||||
if (d1.first + d2.first + d3.first == pointsLeft) {
|
|
||||||
checkouts.push_back({d1.second, d2.second, d3.second});
|
|
||||||
if ((int)checkouts.size() >= MAX_RESULTS)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
done:
|
|
||||||
if (checkouts.empty()) {
|
|
||||||
print("No checkout possible for " + std::to_string(pointsLeft) +
|
|
||||||
" points.");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Possible checkouts (showing up to " + std::to_string(MAX_RESULTS) +
|
|
||||||
"):");
|
|
||||||
std::vector<int> firstCheckout;
|
|
||||||
for (auto &combo : checkouts) {
|
|
||||||
std::string line;
|
|
||||||
for (unsigned int i = 0; i < combo.size(); i++) {
|
|
||||||
if (i > 0)
|
|
||||||
line += " \u2192 ";
|
|
||||||
line += combo[i];
|
|
||||||
}
|
|
||||||
print(line);
|
|
||||||
}
|
|
||||||
return firstCheckout;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
make
|
|
||||||
./darts
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
CXX := g++
|
|
||||||
CXXFLAGS := -O2 -Wall -Wextra -std=c++17
|
|
||||||
LDFLAGS :=
|
|
||||||
|
|
||||||
SRC := main.cpp
|
|
||||||
BIN := find_percentage
|
|
||||||
|
|
||||||
all: $(BIN)
|
|
||||||
|
|
||||||
$(BIN): $(SRC)
|
|
||||||
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
run: $(BIN)
|
|
||||||
./$(BIN)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(BIN)
|
|
||||||
|
|
||||||
.PHONY: all run clean
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
#include <cmath>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
make
|
|
||||||
./find_percentage
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user