diff --git a/output_path/test/src/resources/puzzles/sudoku/rules/LastCellForNumberDirectRule/TestBoard b/output_path/test/src/resources/puzzles/sudoku/rules/LastCellForNumberDirectRule/TestBoard new file mode 100644 index 000000000..a41ad749c --- /dev/null +++ b/output_path/test/src/resources/puzzles/sudoku/rules/LastCellForNumberDirectRule/TestBoard @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/output_path/test/src/resources/puzzles/sudoku/rules/LastNumberForCellDirectRule/FullRegion b/output_path/test/src/resources/puzzles/sudoku/rules/LastNumberForCellDirectRule/FullRegion new file mode 100644 index 000000000..49dae4aa6 --- /dev/null +++ b/output_path/test/src/resources/puzzles/sudoku/rules/LastNumberForCellDirectRule/FullRegion @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/output_path/test/src/resources/puzzles/sudoku/rules/RepeatedNumberContradictionRule/a b/output_path/test/src/resources/puzzles/sudoku/rules/RepeatedNumberContradictionRule/a new file mode 100644 index 000000000..e69de29bb diff --git a/puzzles files/binary/10x10 Binary Hard/10x10 Binary Hard 1 b/puzzles files/binary/10x10 Binary Hard/10x10 Binary Hard 1 new file mode 100644 index 000000000..42ccf371b --- /dev/null +++ b/puzzles files/binary/10x10 Binary Hard/10x10 Binary Hard 1 @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/10x10 Binary Hard/10x10 Binary Hard 2 b/puzzles files/binary/10x10 Binary Hard/10x10 Binary Hard 2 new file mode 100644 index 000000000..d73caa5d2 --- /dev/null +++ b/puzzles files/binary/10x10 Binary Hard/10x10 Binary Hard 2 @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/10x10 Binary Hard/10x10 Binary Hard 3 b/puzzles files/binary/10x10 Binary Hard/10x10 Binary Hard 3 new file mode 100644 index 000000000..99ec9769b --- /dev/null +++ b/puzzles files/binary/10x10 Binary Hard/10x10 Binary Hard 3 @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/10x10 Binary Medium/10x10 Binary Medium 1 b/puzzles files/binary/10x10 Binary Medium/10x10 Binary Medium 1 new file mode 100644 index 000000000..d203617c8 --- /dev/null +++ b/puzzles files/binary/10x10 Binary Medium/10x10 Binary Medium 1 @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/10x10 Binary Medium/10x10 Binary Medium 2 b/puzzles files/binary/10x10 Binary Medium/10x10 Binary Medium 2 new file mode 100644 index 000000000..db56f04f3 --- /dev/null +++ b/puzzles files/binary/10x10 Binary Medium/10x10 Binary Medium 2 @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/10x10 Binary Medium/10x10 Binary Medium 3 b/puzzles files/binary/10x10 Binary Medium/10x10 Binary Medium 3 new file mode 100644 index 000000000..11940a6eb --- /dev/null +++ b/puzzles files/binary/10x10 Binary Medium/10x10 Binary Medium 3 @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/10x10 Binary Very Hard/10x10 Binary Very Hard 1 b/puzzles files/binary/10x10 Binary Very Hard/10x10 Binary Very Hard 1 new file mode 100644 index 000000000..828a450cf --- /dev/null +++ b/puzzles files/binary/10x10 Binary Very Hard/10x10 Binary Very Hard 1 @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/6x6 Binary Easy/6x6 Binary Easy 1 b/puzzles files/binary/6x6 Binary Easy/6x6 Binary Easy 1 new file mode 100644 index 000000000..7b22ffc10 --- /dev/null +++ b/puzzles files/binary/6x6 Binary Easy/6x6 Binary Easy 1 @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/binary/6x6 Binary Easy/6x6 Binary Easy 2 b/puzzles files/binary/6x6 Binary Easy/6x6 Binary Easy 2 new file mode 100644 index 000000000..ea8ef93b0 --- /dev/null +++ b/puzzles files/binary/6x6 Binary Easy/6x6 Binary Easy 2 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/binary/6x6 Binary Easy/6x6 Binary Easy 3 b/puzzles files/binary/6x6 Binary Easy/6x6 Binary Easy 3 new file mode 100644 index 000000000..0f0ff745e --- /dev/null +++ b/puzzles files/binary/6x6 Binary Easy/6x6 Binary Easy 3 @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/binary/6x6 Binary Easy/6x6 Binary Easy 4 b/puzzles files/binary/6x6 Binary Easy/6x6 Binary Easy 4 new file mode 100644 index 000000000..da76d067b --- /dev/null +++ b/puzzles files/binary/6x6 Binary Easy/6x6 Binary Easy 4 @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/binary/6x6 Binary Easy/6x6 Binary Easy 5 b/puzzles files/binary/6x6 Binary Easy/6x6 Binary Easy 5 new file mode 100644 index 000000000..a1ea13988 --- /dev/null +++ b/puzzles files/binary/6x6 Binary Easy/6x6 Binary Easy 5 @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/6x6 Binary Hard/6x6 Binary Hard 1 b/puzzles files/binary/6x6 Binary Hard/6x6 Binary Hard 1 new file mode 100644 index 000000000..5f7f72a8a --- /dev/null +++ b/puzzles files/binary/6x6 Binary Hard/6x6 Binary Hard 1 @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/6x6 Binary Hard/6x6 Binary Hard 2 b/puzzles files/binary/6x6 Binary Hard/6x6 Binary Hard 2 new file mode 100644 index 000000000..a4ed30c31 --- /dev/null +++ b/puzzles files/binary/6x6 Binary Hard/6x6 Binary Hard 2 @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/6x6 Binary Hard/6x6 Binary Hard 3 b/puzzles files/binary/6x6 Binary Hard/6x6 Binary Hard 3 new file mode 100644 index 000000000..fc0e413c1 --- /dev/null +++ b/puzzles files/binary/6x6 Binary Hard/6x6 Binary Hard 3 @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/6x6 Binary Medium/6x6 Binary Medium 1 b/puzzles files/binary/6x6 Binary Medium/6x6 Binary Medium 1 new file mode 100644 index 000000000..a5ab8a2dc --- /dev/null +++ b/puzzles files/binary/6x6 Binary Medium/6x6 Binary Medium 1 @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/6x6 Binary Medium/6x6 Binary Medium 2 b/puzzles files/binary/6x6 Binary Medium/6x6 Binary Medium 2 new file mode 100644 index 000000000..4be5fdaad --- /dev/null +++ b/puzzles files/binary/6x6 Binary Medium/6x6 Binary Medium 2 @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/6x6 Binary Medium/6x6 Binary Medium 3 b/puzzles files/binary/6x6 Binary Medium/6x6 Binary Medium 3 new file mode 100644 index 000000000..eba370cab --- /dev/null +++ b/puzzles files/binary/6x6 Binary Medium/6x6 Binary Medium 3 @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/6x6 Binary Very Hard/6x6 Binary Very Hard 1 b/puzzles files/binary/6x6 Binary Very Hard/6x6 Binary Very Hard 1 new file mode 100644 index 000000000..faa68fa5e --- /dev/null +++ b/puzzles files/binary/6x6 Binary Very Hard/6x6 Binary Very Hard 1 @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/6x6 Binary Very Hard/6x6 Binary Very Hard 2 b/puzzles files/binary/6x6 Binary Very Hard/6x6 Binary Very Hard 2 new file mode 100644 index 000000000..3c707bdaa --- /dev/null +++ b/puzzles files/binary/6x6 Binary Very Hard/6x6 Binary Very Hard 2 @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/6x6 Binary Very Hard/6x6 Binary Very Hard 3 b/puzzles files/binary/6x6 Binary Very Hard/6x6 Binary Very Hard 3 new file mode 100644 index 000000000..217a032d8 --- /dev/null +++ b/puzzles files/binary/6x6 Binary Very Hard/6x6 Binary Very Hard 3 @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/8x8 Binary Easy/8x8 Binary Easy 1 b/puzzles files/binary/8x8 Binary Easy/8x8 Binary Easy 1 new file mode 100644 index 000000000..befd674f9 --- /dev/null +++ b/puzzles files/binary/8x8 Binary Easy/8x8 Binary Easy 1 @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/8x8 Binary Easy/8x8 Binary Easy 2 b/puzzles files/binary/8x8 Binary Easy/8x8 Binary Easy 2 new file mode 100644 index 000000000..724426c26 --- /dev/null +++ b/puzzles files/binary/8x8 Binary Easy/8x8 Binary Easy 2 @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/8x8 Binary Easy/8x8 Binary Easy 3 b/puzzles files/binary/8x8 Binary Easy/8x8 Binary Easy 3 new file mode 100644 index 000000000..92a96c72b --- /dev/null +++ b/puzzles files/binary/8x8 Binary Easy/8x8 Binary Easy 3 @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/8x8 Binary Hard/8x8 Binary Hard 1 b/puzzles files/binary/8x8 Binary Hard/8x8 Binary Hard 1 new file mode 100644 index 000000000..34eaf8388 --- /dev/null +++ b/puzzles files/binary/8x8 Binary Hard/8x8 Binary Hard 1 @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/8x8 Binary Hard/8x8 Binary Hard 2 b/puzzles files/binary/8x8 Binary Hard/8x8 Binary Hard 2 new file mode 100644 index 000000000..9ef23277e --- /dev/null +++ b/puzzles files/binary/8x8 Binary Hard/8x8 Binary Hard 2 @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/8x8 Binary Hard/8x8 Binary Hard 3 b/puzzles files/binary/8x8 Binary Hard/8x8 Binary Hard 3 new file mode 100644 index 000000000..287ff6f68 --- /dev/null +++ b/puzzles files/binary/8x8 Binary Hard/8x8 Binary Hard 3 @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/8x8 Binary Medium/8x8 Binary Medium 1 b/puzzles files/binary/8x8 Binary Medium/8x8 Binary Medium 1 new file mode 100644 index 000000000..47dae23dc --- /dev/null +++ b/puzzles files/binary/8x8 Binary Medium/8x8 Binary Medium 1 @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/8x8 Binary Medium/8x8 Binary Medium 2 b/puzzles files/binary/8x8 Binary Medium/8x8 Binary Medium 2 new file mode 100644 index 000000000..ae4cb8bb0 --- /dev/null +++ b/puzzles files/binary/8x8 Binary Medium/8x8 Binary Medium 2 @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/8x8 Binary Medium/8x8 Binary Medium 3 b/puzzles files/binary/8x8 Binary Medium/8x8 Binary Medium 3 new file mode 100644 index 000000000..2f951ecc4 --- /dev/null +++ b/puzzles files/binary/8x8 Binary Medium/8x8 Binary Medium 3 @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/8x8 Binary Very Hard/8x8 Binary Very Hard 1 b/puzzles files/binary/8x8 Binary Very Hard/8x8 Binary Very Hard 1 new file mode 100644 index 000000000..9c875523b --- /dev/null +++ b/puzzles files/binary/8x8 Binary Very Hard/8x8 Binary Very Hard 1 @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/8x8 Binary Very Hard/8x8 Binary Very Hard 2 b/puzzles files/binary/8x8 Binary Very Hard/8x8 Binary Very Hard 2 new file mode 100644 index 000000000..14f2e4ad2 --- /dev/null +++ b/puzzles files/binary/8x8 Binary Very Hard/8x8 Binary Very Hard 2 @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/binary/8x8 Binary Very Hard/8x8 Binary Very Hard 3 b/puzzles files/binary/8x8 Binary Very Hard/8x8 Binary Very Hard 3 new file mode 100644 index 000000000..ad319a4b7 --- /dev/null +++ b/puzzles files/binary/8x8 Binary Very Hard/8x8 Binary Very Hard 3 @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/light-color-theme.txt b/puzzles files/light-color-theme.txt new file mode 100644 index 000000000..5c45bc71b --- /dev/null +++ b/puzzles files/light-color-theme.txt @@ -0,0 +1,99 @@ +correct: BLUE +incorrect: RED +error: RED_700 +info: GRAY_900 +ui-movement: GRAY_300 +password-field-background: LIGHT_BLUE_400 +password-field-unfocused-background: GRAY_200 +progress-bar-background: GRAY_200 +progress-bar-foreground: LIGHT_BLUE_400 +text-field-background: LIGHT_BLUE_400 +text-field-unfocused-background: GRAY_200 +light-line-border: GRAY_200 +thick-line-border: GRAY_200 +data-selection-background: GRAY +element-view: BLACK +button-highlight: GRAY_300 +button-background: GRAY_200 +button-foreground: BLACK +checkbox-background: WHITE +checkbox-foreground: BLACK +combobox-background: WHITE +combobox-foreground: BLACK +combobox-button-background: GRAY_300 +combobox-selection-background: WHITE +combobox-selection-foreground: BLACK +combobox-selected-in-drop-down-background: GRAY_200 +label-background: WHITE +label-foreground: BLACK +menu-background: LIGHT_BLUE_100 +menu-foreground: BLACK +menu-selection-background: GRAY_200 +menu-selection-foreground: BLACK +menu-disabled-foreground: #000 +menu-bar-background: WHITE +menu-bar-foreground: BLACK +menu-item-disabled-foreground: #000 +menu-item-selection-background: GRAY_200 +menu-item-selection-foreground: BLACK +menu-item-background: WHITE +menu-item-foreground: BLACK +option-pane-background: WHITE +panel-background-color: WHITE +popup-menu-background: WHITE +popup-menu-foreground: BLACK +radio-button-background: WHITE +radio-button-foreground: BLACK +spinner-background: WHITE +spinner-foreground: BLACK +spinner-arrow-button-background: GRAY_200 +scroll-bar-track: GRAY_200 +scroll-bar-thumb: GRAY_300 +scroll-bar-thumb-dark-shadow: GRAY_300 +scroll-bar-thumb-highlight: GRAY_300 +scroll-bar-thumb-shadow: GRAY_300 +scroll-bar-arrow-button-background: GRAY_300 +scroll-pane-background: WHITE +slider-background: WHITE +slider-foreground: GRAY_700 +slider-track-color: BLACK +split-pane-background: WHITE +tabbed-pane-background: WHITE +tabbed-pane-foreground: BLACK +tabbed-pane-highlight: GRAY_200 +tabbed-pane-border-highlight: GRAY_300 +table-selection-background: GRAY_100 +table-selection-foreground: BLACK +table-background: WHITE +table-grid-color: GRAY_200 +table-header-background: GRAY_200 +text-area-background: GRAY_200 +text-area-foreground: BLACK +toggle-button-background: WHITE +toggle-button-foreground: BLACK +tool-bar-background: WHITE +tool-bar-foreground: BLACK +tool-bar-docking-background: LIGHT_GREEN_A100 +tool-bar-floating-background: GRAY_200 +tree-selection-foreground: BLACK +tree-foreground: BLACK +tree-selection-background: GRAY_200 +tree-background: WHITE +radio-button-menu-item-foreground: BLACK +radio-button-menu-item-selection-foreground: BLACK +radio-button-menu-item-selection-background: GRAY_200 +checkbox-menu-item-selection-background: GRAY_200 +checkbox-menu-item-foreground: BLACK +checkbox-menu-item-selection-foreground: BLACK +text-pane-background: GRAY_50 +text-pane-selection-background: LIGHT_BLUE_200 +text-pane-inactive-foreground: GRAY_500 +editor-pane-background: GRAY_50 +editor-pane-selection-background: LIGHT_BLUE_200 +editor-pane-inactive-foreground: GRAY_500 +separator-background: GRAY_300 +separator-foreground: GRAY_300 +tool-tip-background: GRAY_500 +tool-tip-foreground: GRAY_50 +color-chooser-background: WHITE +color-chooser-foreground: BLACK diff --git a/puzzles files/starbattle/5x5 Star Battle 1 star Normal/5x5 Star Battle 1 star Normal 1.xml b/puzzles files/starbattle/5x5 Star Battle 1 star Normal/5x5 Star Battle 1 star Normal 1.xml deleted file mode 100644 index b86e16ff2..000000000 --- a/puzzles files/starbattle/5x5 Star Battle 1 star Normal/5x5 Star Battle 1 star Normal 1.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 Star Battle 1star Normal1.xml b/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 Star Battle 1star Normal1.xml deleted file mode 100644 index 110dd9abe..000000000 --- a/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 Star Battle 1star Normal1.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 Star Battle 1star Normal2.xml b/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 Star Battle 1star Normal2.xml deleted file mode 100644 index 49efeba59..000000000 --- a/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 Star Battle 1star Normal2.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 StarBattle 1star Normal3.xml b/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 StarBattle 1star Normal3.xml deleted file mode 100644 index 855943612..000000000 --- a/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 StarBattle 1star Normal3.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/puzzles files/starbattle/7x7 Star Battle 1 star Hard/7x7 Star Battle 1star Hard1.xml b/puzzles files/starbattle/7x7 Star Battle 1 star Hard/7x7 Star Battle 1star Hard1.xml deleted file mode 100644 index c1d7770f6..000000000 --- a/puzzles files/starbattle/7x7 Star Battle 1 star Hard/7x7 Star Battle 1star Hard1.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal.xml b/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal.xml deleted file mode 100644 index cab0a0a5e..000000000 --- a/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal1.xml b/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal1.xml deleted file mode 100644 index 70b81e376..000000000 --- a/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal1.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal2.xml b/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal2.xml deleted file mode 100644 index c541ece06..000000000 --- a/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal2.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal1.xml b/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal1.xml deleted file mode 100644 index 02dd5d6c0..000000000 --- a/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal1.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal2.xml b/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal2.xml deleted file mode 100644 index 0df84ef62..000000000 --- a/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal2.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal3.xml b/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal3.xml deleted file mode 100644 index 725c91d7f..000000000 --- a/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal3.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/app/Config.java b/src/main/java/edu/rpi/legup/app/Config.java index 8a8e9665d..b04ef9b88 100644 --- a/src/main/java/edu/rpi/legup/app/Config.java +++ b/src/main/java/edu/rpi/legup/app/Config.java @@ -12,6 +12,11 @@ import org.w3c.dom.NodeList; import org.xml.sax.SAXException; +/** + * The {@code Config} class manages the configuration for puzzles by loading configuration data from + * an XML file. It provides methods to access puzzle class names, display names, and their file + * creation statuses + */ public class Config { private static final Logger Logger = LogManager.getLogger(Config.class.getName()); @@ -74,6 +79,13 @@ public static String convertClassNameToDisplayName(String className) { return displayName; } + /** + * Converts the display name of the puzzle back to its corresponding class name. For example: + * convertDisplayNameToClassName("Tree Tent") returns "TreeTent" + * + * @param displayName the display name of the puzzle + * @return the class name of the puzzle as a String + */ public static String convertDisplayNameToClassName(String displayName) { String className = ""; for (int i = 0; i < displayName.length(); i++) { @@ -84,6 +96,11 @@ public static String convertDisplayNameToClassName(String displayName) { return className; } + /** + * Gets a list of all available puzzle display names + * + * @return a List of puzzle display names as Strings + */ public List getPuzzleNames() { List names = new LinkedList(); for (String puzzle : this.getPuzzleClassNames()) { @@ -92,6 +109,12 @@ public List getPuzzleNames() { return names; } + /** + * Returns a list of the display names of the puzzles that can have files created and edited + * within the proof editor + * + * @return a List of puzzle display names as Strings with file creation enabled + */ public List getFileCreationEnabledPuzzleNames() { List names = new LinkedList(); for (String puzzle : this.getFileCreationEnabledPuzzles()) { diff --git a/src/main/java/edu/rpi/legup/app/GameBoardFacade.java b/src/main/java/edu/rpi/legup/app/GameBoardFacade.java index c928c1209..c6f92e5b5 100644 --- a/src/main/java/edu/rpi/legup/app/GameBoardFacade.java +++ b/src/main/java/edu/rpi/legup/app/GameBoardFacade.java @@ -31,6 +31,11 @@ import org.w3c.dom.Node; import org.xml.sax.SAXException; +/** + * {@code GameBoardFacade} is a class designed to manage the game board operations within the + * application. It integrates various components such as UI elements, puzzle management, and history + * tracking + */ public class GameBoardFacade implements IHistorySubject { private static final Logger LOGGER = LogManager.getLogger(GameBoardFacade.class.getName()); @@ -72,6 +77,7 @@ public static synchronized GameBoardFacade getInstance() { return instance; } + /** Initializes the UI components */ public void initializeUI() { EventQueue.invokeLater( () -> { @@ -83,18 +89,25 @@ public void initializeUI() { }); } + /** + * Sets the current puzzle in the game board + * + * @param puzzle the Puzzle to set + */ public void setPuzzle(Puzzle puzzle) { this.puzzle = puzzle; this.puzzleSolver.setPuzzleView(puzzle); this.history.clear(); } + /** Clears the current puzzle */ public void clearPuzzle() { this.puzzle = null; this.curFileName = null; this.history.clear(); } + /** Sets up the configuration by initializing the Config object */ public static void setupConfig() { Config config = null; try { @@ -105,11 +118,21 @@ public static void setupConfig() { GameBoardFacade.getInstance().setConfig(config); } + /** + * Sets the current puzzle editor with the given puzzle + * + * @param puzzle the Puzzle to set in the editor + */ public void setPuzzleEditor(Puzzle puzzle) { this.puzzle = puzzle; this.puzzleEditor.setPuzzleView(puzzle); } + /** + * Sets the configuration object for the GameBoardFacade + * + * @param config config the Config object to set + */ public void setConfig(Config config) { this.config = config; } @@ -168,53 +191,61 @@ public boolean validateTextInput(String game, String[] statements) throws Runtim } /** - * Loads an empty puzzle + * Loads an empty puzzle with the specified dimensions * * @param game name of the puzzle * @param rows the number of rows on the board * @param columns the number of columns on the board */ public void loadPuzzle(String game, int rows, int columns) throws RuntimeException { - String qualifiedClassName = config.getPuzzleClassForName(game); - LOGGER.debug("Loading " + qualifiedClassName); + if (!game.isEmpty()) { + String qualifiedClassName = config.getPuzzleClassForName(game); + LOGGER.debug("Loading " + qualifiedClassName); - try { - Class c = Class.forName(qualifiedClassName); - Constructor cons = c.getConstructor(); - Puzzle puzzle = (Puzzle) cons.newInstance(); + try { + Class c = Class.forName(qualifiedClassName); + Constructor cons = c.getConstructor(); + Puzzle puzzle = (Puzzle) cons.newInstance(); - PuzzleImporter importer = puzzle.getImporter(); - if (importer == null) { - LOGGER.error("Puzzle importer is null"); - throw new RuntimeException("Puzzle importer null"); - } + PuzzleImporter importer = puzzle.getImporter(); + if (importer == null) { + LOGGER.error("Puzzle importer is null"); + throw new RuntimeException("Puzzle importer null"); + } - // Theoretically, this exception should never be thrown, since LEGUP should not be - // allowing the user to give row/column input for a puzzle that doesn't support it - if (!importer.acceptsRowsAndColumnsInput()) { - throw new IllegalArgumentException( - puzzle.getName() + " does not accept rows and columns input"); - } + // Theoretically, this exception should never be thrown, since LEGUP should not be + // allowing the user to give row/column input for a puzzle that doesn't support it + if (!importer.acceptsRowsAndColumnsInput()) { + throw new IllegalArgumentException( + puzzle.getName() + " does not accept rows and columns input"); + } - setWindowTitle(puzzle.getName(), "New " + puzzle.getName() + " Puzzle"); - importer.initializePuzzle(rows, columns); + setWindowTitle(puzzle.getName(), "New " + puzzle.getName() + " Puzzle"); + importer.initializePuzzle(rows, columns); - puzzle.initializeView(); - // - // puzzle.getBoardView().onTreeElementChanged(puzzle.getTree().getRootNode()); - setPuzzleEditor(puzzle); - } catch (IllegalArgumentException exception) { - throw new IllegalArgumentException(exception.getMessage()); - } catch (ClassNotFoundException - | NoSuchMethodException - | InvocationTargetException - | IllegalAccessException - | InstantiationException e) { - LOGGER.error(e); - throw new RuntimeException("Puzzle creation error"); + puzzle.initializeView(); + // + // puzzle.getBoardView().onTreeElementChanged(puzzle.getTree().getRootNode()); + setPuzzleEditor(puzzle); + } catch (IllegalArgumentException exception) { + throw new IllegalArgumentException(exception.getMessage()); + } catch (ClassNotFoundException + | NoSuchMethodException + | InvocationTargetException + | IllegalAccessException + | InstantiationException e) { + LOGGER.error(e); + throw new RuntimeException("Puzzle creation error"); + } } } + /** + * Loads an empty puzzle with the specified input + * + * @param game name of the puzzle + * @param statements an array of statements to load the puzzle with + */ public void loadPuzzle(String game, String[] statements) { String qualifiedClassName = config.getPuzzleClassForName(game); LOGGER.debug("Loading " + qualifiedClassName); @@ -257,10 +288,11 @@ public void loadPuzzle(String game, String[] statements) { } /** - * Loads a puzzle file + * Loads a puzzle file from the specified file * * @param fileName file name of the board file - * @throws InvalidFileFormatException if input is invalid + * @throws InvalidFileFormatException if the file format is invalid or if the file cannot be + * created */ public void loadPuzzle(String fileName) throws InvalidFileFormatException { try { @@ -273,6 +305,13 @@ public void loadPuzzle(String fileName) throws InvalidFileFormatException { } } + /** + * Loads a puzzle into the editor from the specified file name + * + * @param fileName the name of the file to load + * @throws InvalidFileFormatException if the file format is invalid or if the file cannot be + * created + */ public void loadPuzzleEditor(String fileName) throws InvalidFileFormatException { try { loadPuzzleEditor(new FileInputStream(fileName)); @@ -284,6 +323,13 @@ public void loadPuzzleEditor(String fileName) throws InvalidFileFormatException } } + /** + * Loads a puzzle into the editor from the specified input stream + * + * @param inputStream the input stream to load the puzzle from + * @throws InvalidFileFormatException if the input stream cannot be processed or the file format + * is invalid + */ public void loadPuzzleEditor(InputStream inputStream) throws InvalidFileFormatException { Document document; try { diff --git a/src/main/java/edu/rpi/legup/app/InvalidConfigException.java b/src/main/java/edu/rpi/legup/app/InvalidConfigException.java index a570e7d44..cad80b5ce 100644 --- a/src/main/java/edu/rpi/legup/app/InvalidConfigException.java +++ b/src/main/java/edu/rpi/legup/app/InvalidConfigException.java @@ -1,6 +1,15 @@ package edu.rpi.legup.app; +/** + * {@code InvalidConfigException} is a custom exception class for handling invalid configuration + * errors + */ public class InvalidConfigException extends Exception { + /** + * Constructs a new InvalidConfigException with the specified detail message + * + * @param message the detail message + */ public InvalidConfigException(String message) { super(message); } diff --git a/src/main/java/edu/rpi/legup/app/LegupPreferences.java b/src/main/java/edu/rpi/legup/app/LegupPreferences.java index 12433d7e4..3d17233bd 100644 --- a/src/main/java/edu/rpi/legup/app/LegupPreferences.java +++ b/src/main/java/edu/rpi/legup/app/LegupPreferences.java @@ -4,6 +4,11 @@ import java.util.Map; import java.util.prefs.Preferences; +/** + * {@code LegupPreferences} is a class responsible for managing user preferences within the + * application. It uses Java's Preferences API to store and retrieve preferences, and it provides + * methods for accessing and updating these preferences. + */ public class LegupPreferences { private static LegupPreferences instance; @@ -73,9 +78,10 @@ public class LegupPreferences { } /** - * Gets the legup preferences singleton instance. + * Gets the legup preferences singleton instance This method ensures that only one instance of + * LegupPreferences exists * - * @return legup preferences + * @return the singleton instance of LegupPreferences */ public static LegupPreferences getInstance() { if (instance == null) { @@ -84,30 +90,40 @@ public static LegupPreferences getInstance() { return instance; } - /** Private LegupPreferences Singleton Constructor */ + /** + * Private constructor to prevent instantiation from outside the class Use {@link + * #getInstance()} to access the singleton instance + */ private LegupPreferences() {} /** * Gets the user preference by the string key * * @param key key name of the preference - * @return value of the preference + * @return value of the preference or {@code null} if the preference does not exist */ public String getUserPref(String key) { return preferencesMap.get(key); } /** - * Gets the user preference by the string key, value pair + * Sets the user preference for the specified key to the provided value * - * @param key key name of the preference - * @param value value of the preference + * @param key key to set for the preference + * @param value value to set for the preference */ public void setUserPref(String key, String value) { preferences.put(key, value); preferencesMap.put(key, value); } + /** + * Retrieves the user preference associated with the specified key as a boolean + * + * @param key the key for the preference to retrieve + * @return the boolean value of the preference + * @throws RuntimeException if the preference value cannot be interpreted as a boolean + */ public boolean getUserPrefAsBool(String key) { if (preferencesMap.get(key).equalsIgnoreCase(Boolean.toString(true))) { return true; @@ -120,10 +136,20 @@ public boolean getUserPrefAsBool(String key) { } } + /** + * Gets the saved path + * + * @return the saved path as a String + */ public String getSavedPath() { return SAVED_PATH; } + /** + * Sets the saved path to the specified value + * + * @param path the new saved path + */ public void setSavedPath(String path) { SAVED_PATH = path; } diff --git a/src/main/java/edu/rpi/legup/controller/Controller.java b/src/main/java/edu/rpi/legup/controller/Controller.java index 57ce107ac..823bac716 100644 --- a/src/main/java/edu/rpi/legup/controller/Controller.java +++ b/src/main/java/edu/rpi/legup/controller/Controller.java @@ -5,6 +5,11 @@ import java.awt.event.*; import javax.swing.*; +/** + * {@code Controller} is an abstract class designed to handle various mouse events and provide + * control functionality for a {@code ScrollView}. It implements several mouse event interfaces to + * manage interactions such as panning and zooming within a {@code ScrollView} + */ public abstract class Controller implements MouseMotionListener, MouseListener, MouseWheelListener { protected ScrollView viewer; private int x, y; @@ -19,6 +24,11 @@ public Controller() { pan = false; } + /** + * Sets the ScrollView instance that this controller manages + * + * @param viewer The ScrollView instance to be set + */ public void setViewer(ScrollView viewer) { this.viewer = viewer; } diff --git a/src/main/java/edu/rpi/legup/controller/CursorController.java b/src/main/java/edu/rpi/legup/controller/CursorController.java index 2706bd522..aad7eaf46 100644 --- a/src/main/java/edu/rpi/legup/controller/CursorController.java +++ b/src/main/java/edu/rpi/legup/controller/CursorController.java @@ -6,6 +6,11 @@ import java.util.Timer; import java.util.TimerTask; +/** + * {@code CursorController} provides functionality for managing the cursor appearance during actions + * that take a certain amount of time. It allows for the display of a busy cursor while an action is + * being processed and reverts to the default cursor afterward + */ public class CursorController { public static final Cursor BUSY_CURSOR = new Cursor(Cursor.WAIT_CURSOR); public static final Cursor DEFAULT_CURSOR = new Cursor(Cursor.DEFAULT_CURSOR); diff --git a/src/main/java/edu/rpi/legup/controller/EditorElementController.java b/src/main/java/edu/rpi/legup/controller/EditorElementController.java index 5a23885af..050f5bd65 100644 --- a/src/main/java/edu/rpi/legup/controller/EditorElementController.java +++ b/src/main/java/edu/rpi/legup/controller/EditorElementController.java @@ -9,6 +9,11 @@ import java.awt.event.ActionListener; import javax.swing.*; +/** + * {@code EditorElementController} manages actions related to UI elements within the puzzle editor + * environment. It handles button presses, updates element selection, and manages visual states of + * buttons + */ public class EditorElementController implements ActionListener { protected Object lastSource; protected ElementController elementController; @@ -20,19 +25,33 @@ public EditorElementController() { prevButton = null; } + /** + * Sets the ElementController instance for this controller + * + * @param elementController the ElementController instance to be set + */ public void setElementController(ElementController elementController) { this.elementController = elementController; } + /** + * Handles the event when a button associated with an Element is pressed + * + * @param element the Element associated with the button that was pressed + */ public void buttonPressed(Element element) { // TODO: implement what happens when element is pressed - System.out.printf("%s button pressed!\n", element.getElementName()); if (elementController != null) { elementController.setSelectedElement(element); } } + /** + * Handles action events triggered by buttons + * + * @param e the event to be processed + */ @Override public void actionPerformed(ActionEvent e) { lastSource = e.getSource(); diff --git a/src/main/java/edu/rpi/legup/controller/ElementController.java b/src/main/java/edu/rpi/legup/controller/ElementController.java index 5840650e1..436b078b9 100644 --- a/src/main/java/edu/rpi/legup/controller/ElementController.java +++ b/src/main/java/edu/rpi/legup/controller/ElementController.java @@ -26,13 +26,17 @@ import java.awt.*; import java.awt.event.*; +/** + * The ElementController class manages UI interactions related to elements in a {@link BoardView}. + * It handles mouse events, key events, and actions related to element selection and manipulation + */ public class ElementController implements MouseListener, MouseMotionListener, ActionListener, KeyListener { protected BoardView boardView; private Element selectedElement; /** - * ElementController Constructor controller to handles ui events associated interacting with a + * ElementController Constructor controller to handle ui events associated interacting with a * {@link BoardView} */ public ElementController() { @@ -86,6 +90,7 @@ public void mouseReleased(MouseEvent e) { if (boardView == null) { boardView = getInstance().getLegupUI().getEditorBoardView(); } + Board board = boardView.getBoard(); ElementView elementView = boardView.getElement(e.getPoint()); TreeViewSelection selection = null; @@ -137,17 +142,8 @@ public void mouseReleased(MouseEvent e) { if (this.boardView.getBoard() instanceof TreeTentBoard) { scaledPoint.setLocation(scaledPoint.getX() - 1, scaledPoint.getY() - 1); } - System.out.printf( - "selected Element is NOT null, attempting to change board at (%d, %d)\n", - scaledPoint.x, scaledPoint.y); - // System.out.println("Before: " + b.getCell(scaledPoint.x, - // scaledPoint.y).getData()); + b.setCell(scaledPoint.x, scaledPoint.y, this.selectedElement, e); - // System.out.println("After: " + b.getCell(scaledPoint.x, - // scaledPoint.y).getData()); - // } else { - // System.out.println("selected Element is null!"); - // } boardView.repaint(); } diff --git a/src/main/java/edu/rpi/legup/controller/RuleController.java b/src/main/java/edu/rpi/legup/controller/RuleController.java index 6e88dc4be..df4def59e 100644 --- a/src/main/java/edu/rpi/legup/controller/RuleController.java +++ b/src/main/java/edu/rpi/legup/controller/RuleController.java @@ -16,6 +16,11 @@ import java.awt.event.ActionListener; import java.util.List; +/** + * The RuleController class is responsible for handling UI events related to rule buttons in the + * RulePanel of the Legup application. It implements ActionListener to process action events + * triggered by rule buttons and applies rules to the puzzle accordingly + */ public class RuleController implements ActionListener { protected Object lastSource; diff --git a/src/main/java/edu/rpi/legup/controller/TreeController.java b/src/main/java/edu/rpi/legup/controller/TreeController.java index 80fdee1af..6e4ce9fd9 100644 --- a/src/main/java/edu/rpi/legup/controller/TreeController.java +++ b/src/main/java/edu/rpi/legup/controller/TreeController.java @@ -11,6 +11,10 @@ import java.awt.event.MouseWheelEvent; import javax.swing.*; +/** + * The TreeController class handles UI events from a TreePanel. It extends the Controller class to + * provide specific behavior for tree interactions + */ public class TreeController extends Controller { /** * TreeController Constructor creates a controller object to listen to ui events from a {@link diff --git a/src/main/java/edu/rpi/legup/history/AddTreeElementCommand.java b/src/main/java/edu/rpi/legup/history/AddTreeElementCommand.java index 12b39cd85..421cea438 100644 --- a/src/main/java/edu/rpi/legup/history/AddTreeElementCommand.java +++ b/src/main/java/edu/rpi/legup/history/AddTreeElementCommand.java @@ -10,6 +10,10 @@ import java.util.List; import java.util.Map; +/** + * The AddTreeElementCommand class represents a command to add tree elements to the proof tree. It + * extends the PuzzleCommand class to handle the addition of tree elements and undo operation. + */ public class AddTreeElementCommand extends PuzzleCommand { private TreeViewSelection selection; @@ -27,7 +31,10 @@ public AddTreeElementCommand(TreeViewSelection selection) { this.addChild = new HashMap<>(); } - /** Executes an command */ + /** + * Executes the command to add selected tree elements to the tree. Updates the puzzle and tree + * view accordingly + */ @Override public void executeCommand() { Tree tree = GameBoardFacade.getInstance().getTree(); @@ -95,7 +102,10 @@ public String getErrorString() { return null; } - /** Undoes an command */ + /** + * Undoes the command by removing the added tree elements. Updates the puzzle and tree view + * accordingly + */ @Override public void undoCommand() { Tree tree = GameBoardFacade.getInstance().getTree(); diff --git a/src/main/java/edu/rpi/legup/history/ApplyDefaultDirectRuleCommand.java b/src/main/java/edu/rpi/legup/history/ApplyDefaultDirectRuleCommand.java index 02dffae44..e3e89ddd0 100644 --- a/src/main/java/edu/rpi/legup/history/ApplyDefaultDirectRuleCommand.java +++ b/src/main/java/edu/rpi/legup/history/ApplyDefaultDirectRuleCommand.java @@ -10,6 +10,11 @@ import java.util.List; import java.util.Map; +/** + * The ApplyDefaultDirectRuleCommand class represents a command to apply a default direct rule to + * selected tree nodes in the proof tree. It extends the PuzzleCommand class to handle rule + * application and undo operation. + */ public class ApplyDefaultDirectRuleCommand extends PuzzleCommand { private TreeViewSelection selection; @@ -69,7 +74,10 @@ public String getErrorString() { return null; } - /** Executes an command */ + /** + * Executes the command to apply the default rule to the selected tree nodes. Updates the puzzle + * and tree view accordingly. + */ @Override public void executeCommand() { Tree tree = GameBoardFacade.getInstance().getTree(); @@ -109,7 +117,10 @@ public void executeCommand() { puzzle.notifyTreeListeners(listener -> listener.onTreeSelectionChanged(newSelection)); } - /** Undoes an command */ + /** + * Undoes the command by removing the applied default rule from the tree nodes. Updates the + * puzzle and tree view accordingly. + */ @Override public void undoCommand() { Puzzle puzzle = GameBoardFacade.getInstance().getPuzzleModule(); diff --git a/src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java b/src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java index 97192e145..4717b25ae 100644 --- a/src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java +++ b/src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java @@ -13,6 +13,11 @@ import java.awt.event.MouseEvent; import java.util.*; +/** + * The AutoCaseRuleCommand class represents a command to automatically apply a case rule to a + * selected tree node in the proof tree. It extends the PuzzleCommand class to handle case rule + * application and undo operation. + */ public class AutoCaseRuleCommand extends PuzzleCommand { private ElementView elementView; @@ -46,7 +51,10 @@ public AutoCaseRuleCommand( this.caseTrans = new ArrayList<>(); } - /** Executes an command */ + /** + * Executes the command to apply the case rule to the selected tree node. Updates the puzzle and + * tree view accordingly. + */ @Override public void executeCommand() { Tree tree = getInstance().getTree(); @@ -60,7 +68,7 @@ public void executeCommand() { caseRule.getCases(caseBoard.getBaseBoard(), elementView.getPuzzleElement()); for (Board board : cases) { final TreeTransition transition = (TreeTransition) tree.addTreeElement(node); - board.setModifiable(false); + // board.setModifiable(false); transition.setBoard(board); transition.setRule(caseRule); transition.setSelection(elementView.getPuzzleElement().copy()); @@ -129,7 +137,10 @@ public String getErrorString() { return null; } - /** Undoes an command */ + /** + * Undoes the command by removing the applied case rules from the tree node. Updates the puzzle + * and tree view accordingly + */ @Override public void undoCommand() { Puzzle puzzle = GameBoardFacade.getInstance().getPuzzleModule(); diff --git a/src/main/java/edu/rpi/legup/history/CommandError.java b/src/main/java/edu/rpi/legup/history/CommandError.java index 35b7bb15b..78027f6ab 100644 --- a/src/main/java/edu/rpi/legup/history/CommandError.java +++ b/src/main/java/edu/rpi/legup/history/CommandError.java @@ -1,10 +1,17 @@ package edu.rpi.legup.history; +/** + * The CommandError enum represents various error conditions that can occur when executing or + * validating commands related to tree elements in the proof tree. Each error condition is + * associated with a descriptive message. + */ public enum CommandError { NO_SELECTED_VIEWS("The selection does not have any tree elements."), ONE_SELECTED_VIEW("The selection must have exactly one tree element."), UNMODIFIABLE_BOARD("The selection contains a board which is not modifiable."), UNMODIFIABLE_DATA("The selection contains a board where the data element is not modifiable."), + UNMODIFIABLE_DATA_CASE_RULE( + "The proof tree contains a future case rule, causing previous transitions to become unmodifiable."), CONTAINS_ROOT("The selection contains the root tree node."), ONE_CHILD("The selection contains a tree node that does not have exactly one child."), ADD_WITH_CHILD("The selection contains a tree transition that already has a child tree node."), @@ -18,10 +25,20 @@ public enum CommandError { private String value; + /** + * Constructs a CommandError with the specified error message + * + * @param value The error message associated with the command error + */ CommandError(String value) { this.value = value; } + /** + * Returns the error message associated with this CommandError + * + * @return The error message + */ @Override public String toString() { return value; diff --git a/src/main/java/edu/rpi/legup/history/CommandState.java b/src/main/java/edu/rpi/legup/history/CommandState.java index f47c0405d..b72f15480 100644 --- a/src/main/java/edu/rpi/legup/history/CommandState.java +++ b/src/main/java/edu/rpi/legup/history/CommandState.java @@ -1,5 +1,9 @@ package edu.rpi.legup.history; +/** + * The CommandState enum represents the various states that a command can be in during its + * lifecycle. Each state is associated with a descriptive name. + */ public enum CommandState { CREATED("Created"), EXECUTED("Executed"), @@ -8,10 +12,20 @@ public enum CommandState { private String value; + /** + * Constructs a CommandState with the specified state name + * + * @param value The name associated with the command state + */ CommandState(String value) { this.value = value; } + /** + * Returns the name associated with this CommandState + * + * @return The state name + */ @Override public String toString() { return value; diff --git a/src/main/java/edu/rpi/legup/history/DeleteTreeElementCommand.java b/src/main/java/edu/rpi/legup/history/DeleteTreeElementCommand.java index 0469685c1..bfd9cd7f5 100644 --- a/src/main/java/edu/rpi/legup/history/DeleteTreeElementCommand.java +++ b/src/main/java/edu/rpi/legup/history/DeleteTreeElementCommand.java @@ -7,6 +7,11 @@ import edu.rpi.legup.ui.proofeditorui.treeview.*; import java.util.List; +/** + * The DeleteTreeElementCommand class represents a command to delete tree elements from a proof + * tree. It extends PuzzleCommand and implements the functionality to remove selected tree elements + * and handle undo operations. + */ public class DeleteTreeElementCommand extends PuzzleCommand { private TreeViewSelection selection; @@ -20,25 +25,33 @@ public DeleteTreeElementCommand(TreeViewSelection selection) { this.selection = selection.copy(); } - /** Executes an command */ + /** Executes the delete command, removing the selected tree elements from the tree. */ @Override public void executeCommand() { Tree tree = GameBoardFacade.getInstance().getTree(); Puzzle puzzle = GameBoardFacade.getInstance().getPuzzleModule(); List selectedViews = selection.getSelectedViews(); + if (selectedViews.isEmpty()) { + return; + } TreeElementView firstSelectedView = selectedViews.get(0); TreeElementView newSelectedView; if (firstSelectedView.getType() == TreeElementType.NODE) { + // System.out.println("FIRST SELECTION NODE, total selection views: " + + // selectedViews.size()); TreeNodeView nodeView = (TreeNodeView) firstSelectedView; newSelectedView = nodeView.getParentView(); } else { + // System.out.println("FIRST SELECTION TRANS, total selection views: " + + // selectedViews.size()); TreeTransitionView transitionView = (TreeTransitionView) firstSelectedView; newSelectedView = transitionView.getParentViews().get(0); } for (TreeElementView selectedView : selectedViews) { + System.out.println("DELETED"); TreeElement element = selectedView.getTreeElement(); tree.removeTreeElement(element); puzzle.notifyTreeListeners(listener -> listener.onTreeElementRemoved(element)); @@ -73,7 +86,7 @@ public String getErrorString() { return null; } - /** Undoes an command */ + /** Undoes the delete command, re-adding the previously deleted tree elements. */ @Override public void undoCommand() { Puzzle puzzle = GameBoardFacade.getInstance().getPuzzleModule(); diff --git a/src/main/java/edu/rpi/legup/history/EditDataCommand.java b/src/main/java/edu/rpi/legup/history/EditDataCommand.java index d65f03d66..0d51cc717 100644 --- a/src/main/java/edu/rpi/legup/history/EditDataCommand.java +++ b/src/main/java/edu/rpi/legup/history/EditDataCommand.java @@ -9,10 +9,18 @@ import edu.rpi.legup.model.tree.*; import edu.rpi.legup.ui.boardview.BoardView; import edu.rpi.legup.ui.boardview.ElementView; +import edu.rpi.legup.ui.lookandfeel.materialdesign.MaterialColors; import edu.rpi.legup.ui.proofeditorui.treeview.*; +import java.awt.*; import java.awt.event.MouseEvent; import java.util.List; +import javax.swing.*; +/** + * The EditDataCommand class represents a command to edit the data of a puzzle element within a tree + * transition. It extends PuzzleCommand and provides functionality to execute and undo changes made + * to puzzle elements. + */ public class EditDataCommand extends PuzzleCommand { private TreeTransition transition; private PuzzleElement savePuzzleElement; @@ -26,7 +34,7 @@ public class EditDataCommand extends PuzzleCommand { * EditDataCommand Constructor create a puzzle command for editing a board * * @param elementView currently selected puzzle puzzleElement view that is being edited - * @param selection currently selected tree puzzleElement views that is being edited + * @param selection currently selected tree puzzleElement views that are being edited * @param event mouse event */ public EditDataCommand(ElementView elementView, TreeViewSelection selection, MouseEvent event) { @@ -38,7 +46,7 @@ public EditDataCommand(ElementView elementView, TreeViewSelection selection, Mou this.transition = null; } - /** Executes a command */ + /** Executes the edit data command, modifying the puzzle element and propagating changes */ @SuppressWarnings("unchecked") @Override public void executeCommand() { @@ -54,16 +62,13 @@ public void executeCommand() { if (treeElement.getType() == TreeElementType.NODE) { TreeNode treeNode = (TreeNode) treeElement; - if (treeNode.getChildren().isEmpty()) { if (transition == null) { transition = tree.addNewTransition(treeNode); } puzzle.notifyTreeListeners(listener -> listener.onTreeElementAdded(transition)); } - board = transition.getBoard(); - puzzleElement = board.getPuzzleElement(selectedPuzzleElement); savePuzzleElement = puzzleElement.copy(); } else { @@ -73,7 +78,6 @@ public void executeCommand() { } Board prevBoard = transition.getParents().get(0).getBoard(); - boardView.getElementController().changeCell(event, puzzleElement); if (prevBoard.getPuzzleElement(selectedPuzzleElement).equalsData(puzzleElement)) { @@ -102,35 +106,50 @@ public void executeCommand() { public String getErrorString() { List selectedViews = selection.getSelectedViews(); if (selectedViews.size() != 1) { + flashTreeViewRed(); return CommandError.ONE_SELECTED_VIEW.toString(); } TreeElementView selectedView = selection.getFirstSelection(); Board board = selectedView.getTreeElement().getBoard(); PuzzleElement selectedPuzzleElement = elementView.getPuzzleElement(); if (selectedView.getType() == TreeElementType.NODE) { - TreeNodeView nodeView = (TreeNodeView) selectedView; if (!nodeView.getChildrenViews().isEmpty()) { + flashTreeViewRed(); return CommandError.UNMODIFIABLE_BOARD.toString(); - } else { - if (!board.getPuzzleElement(selectedPuzzleElement).isModifiable()) { - return CommandError.UNMODIFIABLE_DATA.toString(); - } + } else if (!board.getPuzzleElement(selectedPuzzleElement).isModifiable()) { + flashTreeViewRed(); + return CommandError.UNMODIFIABLE_DATA.toString(); } } else { TreeTransitionView transitionView = (TreeTransitionView) selectedView; if (!transitionView.getTreeElement().getBoard().isModifiable()) { + flashTreeViewRed(); return CommandError.UNMODIFIABLE_BOARD.toString(); } else { if (!board.getPuzzleElement(selectedPuzzleElement).isModifiable()) { + flashTreeViewRed(); return CommandError.UNMODIFIABLE_DATA.toString(); + } else if (!board.getPuzzleElement(selectedPuzzleElement).isModifiableCaseRule()) { + flashTreeViewRed(); + return CommandError.UNMODIFIABLE_DATA_CASE_RULE.toString(); } } } return null; } - /** Undoes an command */ + /** Causes the TreeView background to flash red for a short duration when an error occurs. */ + private void flashTreeViewRed() { + TreeView treeView = getInstance().getLegupUI().getTreePanel().getTreeView(); + Color originalColor = treeView.getBackground(); + treeView.setBackground(MaterialColors.RED_700); + Timer timer = new Timer(400, e -> treeView.setBackground(originalColor)); + timer.setRepeats(false); + timer.start(); + } + + /** Undoes the edit data command, restoring the previous state of the puzzle element. */ @SuppressWarnings("unchecked") @Override public void undoCommand() { diff --git a/src/main/java/edu/rpi/legup/history/History.java b/src/main/java/edu/rpi/legup/history/History.java index 371284f8c..9eaeb3e75 100644 --- a/src/main/java/edu/rpi/legup/history/History.java +++ b/src/main/java/edu/rpi/legup/history/History.java @@ -6,6 +6,11 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +/** + * The History class manages a stack of commands for undo and redo operations on the board and tree + * structure. It maintains a list of commands and a current index to track the position in the + * history stack. + */ public class History { private static final Logger LOGGER = LogManager.getLogger(History.class.getName()); @@ -14,9 +19,8 @@ public class History { private int curIndex; /** - * History Constructor this holds information about changes to the board and Tree structure for - * undoing and redoing operations. Though history is an List, it is implemented like a stack. - * The curIndex points to the top of the stack (where the last change was made). + * Constructs a History object to keep track of changes and allow undo and redo operations. The + * history is implemented as a stack, with curIndex pointing to the top of the stack. */ public History() { history = new ArrayList<>(); @@ -44,13 +48,17 @@ public void pushChange(ICommand command) { } } - /** Undoes an action */ + /** + * Undoes the last action by calling the undo method of the command at the current index. + * Updates the current index and notifies listeners. + */ public void undo() { synchronized (lock) { if (curIndex > -1) { ICommand command = history.get(curIndex--); command.undo(); LOGGER.info("Undoed " + command.getClass().getSimpleName()); + GameBoardFacade.getInstance() .notifyHistoryListeners( l -> l.onUndo(curIndex < 0, curIndex == history.size() - 1)); @@ -58,7 +66,10 @@ public void undo() { } } - /** Redoes an action */ + /** + * Redoes the next action by calling the redo method of the command at the current index. + * Updates the current index and notifies listeners. + */ public void redo() { synchronized (lock) { if (curIndex < history.size() - 1) { @@ -72,7 +83,7 @@ public void redo() { } } - /** Clears all actions from the history stack */ + /** Clears all actions from the history stack and resets the current index */ public void clear() { history.clear(); curIndex = -1; diff --git a/src/main/java/edu/rpi/legup/history/ICommand.java b/src/main/java/edu/rpi/legup/history/ICommand.java index 913d9daaf..fe10bd693 100644 --- a/src/main/java/edu/rpi/legup/history/ICommand.java +++ b/src/main/java/edu/rpi/legup/history/ICommand.java @@ -1,7 +1,11 @@ package edu.rpi.legup.history; +/** + * The ICommand interface defines the structure for command objects in a command pattern. It + * provides methods to execute, undo, redo commands, and to check if a command can be executed. + */ public interface ICommand { - /** Executes a command */ + /** Executes the command. The specific behavior depends on the implementation */ void execute(); /** @@ -19,9 +23,9 @@ public interface ICommand { */ String getError(); - /** Undoes a command */ + /** Undoes the command. Reverts the changes made by the execute method */ void undo(); - /** Redoes a command */ + /** Redoes the command. Re-applies the changes made by the execute method after undoing */ void redo(); } diff --git a/src/main/java/edu/rpi/legup/history/IHistoryListener.java b/src/main/java/edu/rpi/legup/history/IHistoryListener.java index f464941d6..a752cd53a 100644 --- a/src/main/java/edu/rpi/legup/history/IHistoryListener.java +++ b/src/main/java/edu/rpi/legup/history/IHistoryListener.java @@ -1,15 +1,21 @@ package edu.rpi.legup.history; +/** + * The IHistoryListener interface defines methods for listening to changes in the history of + * commands. Implementations of this interface can respond to events related to command history such + * as pushing, undoing, redoing commands, and clearing the history. + */ public interface IHistoryListener { + /** - * Called when a action is pushed onto the edu.rpi.legup.history stack + * Called when a command is pushed onto the history stack. * - * @param command action to push onto the stack + * @param command the command that was pushed onto the stack */ void onPushChange(ICommand command); /** - * Called when an action is undone + * Called when a command is undone. * * @param isBottom true if there are no more actions to undo, false otherwise * @param isTop true if there are no more changes to redo, false otherwise @@ -17,13 +23,13 @@ public interface IHistoryListener { void onUndo(boolean isBottom, boolean isTop); /** - * Called when an action is redone + * Called when a command is redone. * * @param isBottom true if there are no more actions to undo, false otherwise * @param isTop true if there are no more changes to redo, false otherwise */ void onRedo(boolean isBottom, boolean isTop); - /** Called when the edu.rpi.legup.history is cleared */ + /** Called when the history stack is cleared. */ void onClearHistory(); } diff --git a/src/main/java/edu/rpi/legup/history/IHistorySubject.java b/src/main/java/edu/rpi/legup/history/IHistorySubject.java index 78fefff00..b526c7cf3 100644 --- a/src/main/java/edu/rpi/legup/history/IHistorySubject.java +++ b/src/main/java/edu/rpi/legup/history/IHistorySubject.java @@ -2,25 +2,33 @@ import java.util.function.Consumer; +/** + * The IHistorySubject interface defines methods for managing and notifying listeners about changes + * in the command history. Implementations of this interface can add, remove, and notify history + * listeners. + */ public interface IHistorySubject { + /** - * Adds a history listener + * Adds a history listener to receive updates about changes in the command history. * - * @param listener listener to add + * @param listener the listener to add */ void addHistoryListener(IHistoryListener listener); /** - * Removes a history listener + * Removes a history listener, so it no longer receives updates about changes in the command + * history. * - * @param listener listener to remove + * @param listener the listener to remove */ void removeHistoryListener(IHistoryListener listener); /** - * Notifies listeners + * Notifies all registered listeners about a change in the command history. * - * @param algorithm algorithm to notify the listeners with + * @param algorithm a Consumer function that takes an IHistoryListener and performs some action + * with it */ void notifyHistoryListeners(Consumer algorithm); } diff --git a/src/main/java/edu/rpi/legup/history/InvalidCommandStateTransition.java b/src/main/java/edu/rpi/legup/history/InvalidCommandStateTransition.java index 71d072328..0e1ba1b0d 100644 --- a/src/main/java/edu/rpi/legup/history/InvalidCommandStateTransition.java +++ b/src/main/java/edu/rpi/legup/history/InvalidCommandStateTransition.java @@ -1,7 +1,18 @@ package edu.rpi.legup.history; +/** + * The InvalidCommandStateTransition exception is thrown when an invalid state transition is + * attempted on a PuzzleCommand + */ public class InvalidCommandStateTransition extends RuntimeException { + /** + * Constructs a new InvalidCommandStateTransition exception with a detailed message + * + * @param puzzleCommand the PuzzleCommand involved in the invalid transition + * @param from the state from which the transition was attempted + * @param to the state to which the transition was attempted + */ public InvalidCommandStateTransition( PuzzleCommand puzzleCommand, CommandState from, CommandState to) { super( diff --git a/src/main/java/edu/rpi/legup/history/MergeCommand.java b/src/main/java/edu/rpi/legup/history/MergeCommand.java index f234a0884..2d180465b 100644 --- a/src/main/java/edu/rpi/legup/history/MergeCommand.java +++ b/src/main/java/edu/rpi/legup/history/MergeCommand.java @@ -10,6 +10,10 @@ import java.util.List; import java.util.Set; +/** + * The MergeCommand class represents a command to merge selected tree nodes into a single node and + * create a transition to represent the merge + */ public class MergeCommand extends PuzzleCommand { private TreeViewSelection selection; private TreeTransition transition; @@ -24,7 +28,7 @@ public MergeCommand(TreeViewSelection selection) { this.transition = null; } - /** Executes an command */ + /** Executes the merge command */ @Override public void executeCommand() { List selectedViews = selection.getSelectedViews(); @@ -71,7 +75,7 @@ public void executeCommand() { puzzle.notifyTreeListeners(listener -> listener.onTreeSelectionChanged(newSelection)); } - /** Undoes an command */ + /** Undoes the merge command */ @Override public void undoCommand() { Tree tree = GameBoardFacade.getInstance().getTree(); diff --git a/src/main/java/edu/rpi/legup/history/PuzzleCommand.java b/src/main/java/edu/rpi/legup/history/PuzzleCommand.java index 3768e3cbd..54e58c667 100644 --- a/src/main/java/edu/rpi/legup/history/PuzzleCommand.java +++ b/src/main/java/edu/rpi/legup/history/PuzzleCommand.java @@ -1,18 +1,23 @@ package edu.rpi.legup.history; +/** + * The PuzzleCommand class is an abstract base class for commands that can be executed, undone, and + * redone within the puzzle model. It implements the ICommand interface and maintains the state and + * error handling for the command. + */ public abstract class PuzzleCommand implements ICommand { private CommandState state; private boolean isCached; private String cachedError; - /** Puzzle Command Constructor for creating an undoable and redoable change to the model. */ + /** Puzzle Command Constructor for creating an undoable and redoable change to the model */ protected PuzzleCommand() { this.state = CommandState.CREATED; this.isCached = false; this.cachedError = null; } - /** Executes an command */ + /** Executes the command if it can be executed */ @Override public final void execute() { if (canExecute()) { @@ -21,7 +26,7 @@ public final void execute() { } } - /** Determines whether this command can be executed */ + /** Determines whether the command can be executed by checking the error state */ @Override public final boolean canExecute() { cachedError = getError(); @@ -52,13 +57,19 @@ public final String getError() { */ public abstract String getErrorString(); - /** Executes an command */ + /** + * Executes the command. This method must be implemented by subclasses to define the command's + * execution behavior. + */ public abstract void executeCommand(); - /** Undoes an command */ + /** + * Undoes the command. This method must be implemented by subclasses to define the command's + * undo behavior. + */ public abstract void undoCommand(); - /** Redoes an command */ + /** Redoes the command. This method is called if the command was previously undone. */ public void redoCommand() { if (state == CommandState.UNDOED) { executeCommand(); @@ -68,7 +79,7 @@ public void redoCommand() { } } - /** Undoes an command */ + /** Undoes the command if it was executed or redone */ @Override public final void undo() { if (state == CommandState.EXECUTED || state == CommandState.REDOED) { @@ -79,7 +90,7 @@ public final void undo() { } } - /** Redoes an command */ + /** Redoes the command if it was previously undone. */ public final void redo() { if (state == CommandState.UNDOED) { redoCommand(); diff --git a/src/main/java/edu/rpi/legup/history/ValidateCaseRuleCommand.java b/src/main/java/edu/rpi/legup/history/ValidateCaseRuleCommand.java index 7737ecfd3..3f534bcda 100644 --- a/src/main/java/edu/rpi/legup/history/ValidateCaseRuleCommand.java +++ b/src/main/java/edu/rpi/legup/history/ValidateCaseRuleCommand.java @@ -12,6 +12,10 @@ import java.util.List; import java.util.Map; +/** + * The ValidateCaseRuleCommand class represents a command for validating a CaseRule in the tree + * structure. It extends the PuzzleCommand class and implements the ICommand interface. + */ public class ValidateCaseRuleCommand extends PuzzleCommand { private TreeViewSelection selection; @@ -33,7 +37,7 @@ public ValidateCaseRuleCommand(TreeViewSelection selection, CaseRule caseRule) { this.addNode = new HashMap<>(); } - /** Executes an command */ + /** Executes the command to validate the CaseRule */ @Override public void executeCommand() { Tree tree = getInstance().getTree(); @@ -105,7 +109,7 @@ public String getErrorString() { return null; } - /** Undoes an command */ + /** Undoes the validation command, restoring the previous state */ @Override public void undoCommand() { Puzzle puzzle = GameBoardFacade.getInstance().getPuzzleModule(); diff --git a/src/main/java/edu/rpi/legup/history/ValidateContradictionRuleCommand.java b/src/main/java/edu/rpi/legup/history/ValidateContradictionRuleCommand.java index 8737b4008..0a8b9414f 100644 --- a/src/main/java/edu/rpi/legup/history/ValidateContradictionRuleCommand.java +++ b/src/main/java/edu/rpi/legup/history/ValidateContradictionRuleCommand.java @@ -10,6 +10,11 @@ import java.util.List; import java.util.Map; +/** + * The ValidateContradictionRuleCommand class represents a command for validating and applying a + * ContradictionRule within a tree structure. It extends the PuzzleCommand class and implements the + * ICommand interface. + */ public class ValidateContradictionRuleCommand extends PuzzleCommand { private TreeViewSelection selection; @@ -31,7 +36,7 @@ public ValidateContradictionRuleCommand(TreeViewSelection selection, Contradicti this.addTran = new HashMap<>(); } - /** Executes a command */ + /** Executes the command to validate and apply the ContradictionRule. */ @Override public void executeCommand() { Tree tree = GameBoardFacade.getInstance().getTree(); @@ -84,7 +89,11 @@ public void executeCommand() { final TreeElement finalTreeElement; if (firstSelectedView.getType() == TreeElementType.NODE) { TreeNodeView nodeView = (TreeNodeView) firstSelectedView; - finalTreeElement = nodeView.getChildrenViews().get(0).getTreeElement(); + if (!nodeView.getChildrenViews().isEmpty()) { + finalTreeElement = nodeView.getChildrenViews().get(0).getTreeElement(); + } else { + finalTreeElement = null; + } } else { TreeTransitionView transitionView = (TreeTransitionView) firstSelectedView; if (transitionView.getChildView() != null) { @@ -125,7 +134,7 @@ public String getErrorString() { return null; } - /** Undoes a command */ + /** Undoes the validation command, restoring the previous state. */ @Override public void undoCommand() { Puzzle puzzle = GameBoardFacade.getInstance().getPuzzleModule(); diff --git a/src/main/java/edu/rpi/legup/history/ValidateDirectRuleCommand.java b/src/main/java/edu/rpi/legup/history/ValidateDirectRuleCommand.java index d9c063464..49bf378ee 100644 --- a/src/main/java/edu/rpi/legup/history/ValidateDirectRuleCommand.java +++ b/src/main/java/edu/rpi/legup/history/ValidateDirectRuleCommand.java @@ -9,8 +9,16 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +/** + * The ValidateDirectRuleCommand class represents a command for validating and applying a DirectRule + * to a set of selected tree elements. It extends the PuzzleCommand class and implements the + * ICommand interface. + */ public class ValidateDirectRuleCommand extends PuzzleCommand { + private static final Logger LOGGER = LogManager.getLogger(History.class.getName()); private TreeViewSelection selection; private Map oldRules; @@ -30,7 +38,7 @@ public ValidateDirectRuleCommand(TreeViewSelection selection, DirectRule rule) { this.addNode = new HashMap<>(); } - /** Executes an command */ + /** Executes the command to validate and apply the DirectRule. */ @Override public void executeCommand() { Tree tree = GameBoardFacade.getInstance().getTree(); @@ -42,14 +50,15 @@ public void executeCommand() { for (TreeElementView selectedView : selectedViews) { TreeElement element = selectedView.getTreeElement(); TreeTransitionView transitionView; + if (element.getType() == TreeElementType.NODE) { TreeNodeView nodeView = (TreeNodeView) selectedView; transitionView = nodeView.getChildrenViews().get(0); } else { transitionView = (TreeTransitionView) selectedView; } - TreeTransition transition = transitionView.getTreeElement(); + TreeTransition transition = transitionView.getTreeElement(); oldRules.put(transition, transition.getRule()); transition.setRule(newRule); @@ -66,17 +75,41 @@ public void executeCommand() { final TreeNode finalNode = childNode; puzzle.notifyTreeListeners(listener -> listener.onTreeElementAdded(finalNode)); } - newSelection.addToSelection(treeView.getElementView(childNode)); + + TreeElementView childView = treeView.getElementView(childNode); + if (childView == null) { + LOGGER.error("Child view is null for child node: " + childNode); + continue; + } + newSelection.addToSelection(childView); } + TreeElementView firstSelectedView = selection.getFirstSelection(); + final TreeElement finalTreeElement; if (firstSelectedView.getType() == TreeElementType.NODE) { TreeNodeView nodeView = (TreeNodeView) firstSelectedView; + if (nodeView.getChildrenViews().isEmpty()) { + LOGGER.error("NodeView has no children views"); + return; + } finalTreeElement = nodeView.getChildrenViews().get(0).getTreeElement(); } else { TreeTransitionView transitionView = (TreeTransitionView) firstSelectedView; + TreeNodeView childView = transitionView.getChildView(); + if (childView == null) { + LOGGER.error("Child view is null for transition view: " + transitionView); + TreeNode childNode = transitionView.getTreeElement().getChildNode(); + childView = (TreeNodeView) treeView.getElementView(childNode); + transitionView.setChildView(childView); + } + TreeTransition transition = transitionView.getTreeElement(); + if (transition.getParents().get(0).getChildren().isEmpty()) { + transition.getParents().get(0).addChild(transition); + } finalTreeElement = transitionView.getChildView().getTreeElement(); } + puzzle.notifyBoardListeners(listener -> listener.onTreeElementChanged(finalTreeElement)); puzzle.notifyTreeListeners(listener -> listener.onTreeSelectionChanged(newSelection)); } @@ -110,7 +143,7 @@ public String getErrorString() { return null; } - /** Undoes an command */ + /** Undoes the validation command, restoring the previous state. */ @Override public void undoCommand() { Tree tree = GameBoardFacade.getInstance().getTree(); @@ -126,6 +159,7 @@ public void undoCommand() { transitionView = (TreeTransitionView) selectedView; } TreeTransition transition = transitionView.getTreeElement(); + transition.setRule(oldRules.get(transition)); if (addNode.get(transition) != null) { diff --git a/src/main/java/edu/rpi/legup/model/Puzzle.java b/src/main/java/edu/rpi/legup/model/Puzzle.java index 7971c95af..da8f76ddd 100644 --- a/src/main/java/edu/rpi/legup/model/Puzzle.java +++ b/src/main/java/edu/rpi/legup/model/Puzzle.java @@ -35,6 +35,11 @@ import org.w3c.dom.Node; import org.xml.sax.SAXException; +/** + * Abstract class representing a puzzle. The Puzzle class manages the core components of a puzzle + * game, including the board, rules, and elements. It also handles importing and exporting puzzle + * configurations and notifies listeners about changes. + */ public abstract class Puzzle implements IBoardSubject, ITreeSubject { private static final Logger LOGGER = LogManager.getLogger(Puzzle.class.getName()); @@ -53,7 +58,6 @@ public abstract class Puzzle implements IBoardSubject, ITreeSubject { protected List contradictionRules; protected List caseRules; protected List placeableElements; - protected List nonPlaceableElements; /** Puzzle Constructor - creates a new Puzzle */ public Puzzle() { @@ -65,12 +69,15 @@ public Puzzle() { this.caseRules = new ArrayList<>(); this.placeableElements = new ArrayList<>(); - this.nonPlaceableElements = new ArrayList<>(); registerRules(); registerPuzzleElements(); } + /** + * Registers puzzle elements from the package of the derived class. Scans for classes annotated + * with {@link RegisterElement} and initializes them. + */ private void registerPuzzleElements() { String packageName = this.getClass().getPackage().toString().replace("package ", ""); @@ -79,6 +86,11 @@ private void registerPuzzleElements() { for (Class c : possElements) { + String classPackageName = c.getPackage().getName(); + if (!classPackageName.startsWith("edu.rpi.legup.puzzle.") + || !classPackageName.endsWith(".elements")) { + continue; + } System.out.println("possible element: " + c.getName()); // check that the element is not abstract @@ -95,9 +107,6 @@ private void registerPuzzleElements() { case PLACEABLE: this.addPlaceableElement((PlaceableElement) element); break; - case NONPLACEABLE: - this.addNonPlaceableElement((NonPlaceableElement) element); - break; default: break; } @@ -108,19 +117,15 @@ private void registerPuzzleElements() { } } } - - // } catch (IOException | ClassNotFoundException | NoSuchMethodException | - // InstantiationException | IllegalAccessException | - // InvocationTargetException - // e) { - // LOGGER.error("Unable to find rules for " + - // this.getClass().getSimpleName(), e); - // } } catch (Exception e) { LOGGER.error("Unable to find elements for " + this.getClass().getSimpleName(), e); } } + /** + * Registers rules from the package of the derived class. Scans for classes annotated with + * {@link RegisterRule} and initializes them. + */ private void registerRules() { String packageName = this.getClass().getPackage().toString().replace("package ", ""); @@ -129,6 +134,10 @@ private void registerRules() { for (Class c : possRules) { + String classPackageName = c.getPackage().getName(); + if (!classPackageName.contains(".rules")) { + continue; + } System.out.println("possible rule: " + c.getName()); // check that the rule is not abstract @@ -163,14 +172,6 @@ private void registerRules() { } } } - - // } catch (IOException | ClassNotFoundException | NoSuchMethodException | - // InstantiationException | IllegalAccessException | - // InvocationTargetException - // e) { - // LOGGER.error("Unable to find rules for " + - // this.getClass().getSimpleName(), e); - // } } catch (Exception e) { LOGGER.error("Unable to find rules for " + this.getClass().getSimpleName(), e); } @@ -199,10 +200,10 @@ public boolean isValidDimensions(int rows, int columns) { } /** - * Checks if the given array of statements is valid text input for the given puzzle + * Checks if the provided text input is valid for the puzzle. * - * @param statements - * @return + * @param statements array of statements to check + * @return true if input is valid, false otherwise */ public boolean isValidTextInput(String[] statements) { return statements.length > 0; @@ -334,14 +335,15 @@ public List getDirectRules() { return directRules; } + /** + * Gets the list of placeable elements. + * + * @return list of PlaceableElement instances + */ public List getPlaceableElements() { return placeableElements; } - public List getNonPlaceableElements() { - return nonPlaceableElements; - } - /** * Sets the list of direct rules * @@ -360,14 +362,15 @@ public void addDirectRule(DirectRule rule) { directRules.add(rule); } + /** + * Adds a placeable element to this puzzle. + * + * @param element PlaceableElement to add + */ public void addPlaceableElement(PlaceableElement element) { placeableElements.add(element); } - public void addNonPlaceableElement(NonPlaceableElement element) { - nonPlaceableElements.add(element); - } - /** * Remove a basic rule from this Puzzle * @@ -580,9 +583,10 @@ public void setFactory(ElementFactory factory) { } /** - * Adds a board listener + * Adds a board listener to the list of listeners. This allows the puzzle to notify the listener + * about changes to the board. * - * @param listener listener to add + * @param listener The IBoardListener to be added to the list of listeners. */ @Override public void addBoardListener(IBoardListener listener) { @@ -590,9 +594,10 @@ public void addBoardListener(IBoardListener listener) { } /** - * Removes a board listener + * Removes a board listener from the list of listeners. This prevents the puzzle from notifying + * the listener about future changes to the board. * - * @param listener listener to remove + * @param listener The IBoardListener to be removed from the list of listeners. */ @Override public void removeBoardListener(IBoardListener listener) { @@ -600,9 +605,11 @@ public void removeBoardListener(IBoardListener listener) { } /** - * Notifies listeners + * Notifies all registered board listeners about changes. The provided algorithm is applied to + * each listener to process the notification. * - * @param algorithm algorithm to notify the listeners with + * @param algorithm A Consumer function that takes an IBoardListener and performs operations to + * notify the listener. */ @Override public void notifyBoardListeners(Consumer algorithm) { @@ -610,9 +617,10 @@ public void notifyBoardListeners(Consumer algorithm) { } /** - * Adds a board listener + * Adds a tree listener to the list of listeners. This allows the puzzle to notify the listener + * about changes to the tree. * - * @param listener listener to add + * @param listener The ITreeListener to be added to the list of listeners. */ @Override public void addTreeListener(ITreeListener listener) { @@ -620,9 +628,10 @@ public void addTreeListener(ITreeListener listener) { } /** - * Removes a tree listener + * Removes a tree listener from the list of listeners. This prevents the puzzle from notifying + * the listener about future changes to the tree. * - * @param listener listener to remove + * @param listener The ITreeListener to be removed from the list of listeners. */ @Override public void removeTreeListener(ITreeListener listener) { @@ -630,9 +639,11 @@ public void removeTreeListener(ITreeListener listener) { } /** - * Notifies listeners + * Notifies all registered tree listeners about changes. The provided algorithm is applied to + * each listener to process the notification. * - * @param algorithm algorithm to notify the listeners with + * @param algorithm A Consumer function that takes an ITreeListener and performs operations to + * notify the listener. */ @Override public void notifyTreeListeners(Consumer algorithm) { @@ -640,9 +651,10 @@ public void notifyTreeListeners(Consumer algorithm) { } /** - * Check if the puzzle is valid + * Checks if the puzzle is valid. The implementation of this method can vary based on the + * specific criteria for puzzle validity. * - * @return if the puzzle is valid + * @return true if the puzzle is valid, false otherwise. */ public boolean checkValidity() { return true; diff --git a/src/main/java/edu/rpi/legup/model/PuzzleExporter.java b/src/main/java/edu/rpi/legup/model/PuzzleExporter.java index a052a736a..13107bb54 100644 --- a/src/main/java/edu/rpi/legup/model/PuzzleExporter.java +++ b/src/main/java/edu/rpi/legup/model/PuzzleExporter.java @@ -20,6 +20,11 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; +/** + * Abstract class for exporting puzzle data to XML format. This class provides functionality to + * export a puzzle object, including its board and tree structure, to an XML file. Subclasses must + * implement methods to create specific elements for the board and the tree. + */ public abstract class PuzzleExporter { private static final Logger LOGGER = LogManager.getLogger(PuzzleExporter.class.getName()); @@ -91,8 +96,23 @@ public void exportPuzzle(String fileName) throws ExportFileException { } } + /** + * Creates an XML element representing the board of the puzzle. Subclasses must implement this + * method to provide the XML structure for the board. + * + * @param newDocument The XML document to create elements within. + * @return An XML element representing the puzzle's board. + */ protected abstract Element createBoardElement(Document newDocument); + /** + * Creates an XML element representing the proof of the puzzle, including its tree structure. + * This method is used to generate the proof section of the XML, which contains the tree + * representation. + * + * @param newDocument The XML document to create elements within. + * @return An XML element representing the proof of the puzzle. + */ protected Element createProofElement(Document newDocument) { org.w3c.dom.Element proofElement = newDocument.createElement("proof"); org.w3c.dom.Element treeElement = createTreeElement(newDocument); @@ -100,6 +120,13 @@ protected Element createProofElement(Document newDocument) { return proofElement; } + /** + * Creates an XML element representing the tree structure of the puzzle. This method traverses + * the tree nodes and transitions, and creates XML elements for each. + * + * @param newDocument The XML document to create elements within. + * @return An XML element representing the puzzle's tree structure. + */ protected Element createTreeElement(Document newDocument) { org.w3c.dom.Element treeElement = newDocument.createElement("tree"); diff --git a/src/main/java/edu/rpi/legup/model/PuzzleImporter.java b/src/main/java/edu/rpi/legup/model/PuzzleImporter.java index 0902478db..9d16d94e9 100644 --- a/src/main/java/edu/rpi/legup/model/PuzzleImporter.java +++ b/src/main/java/edu/rpi/legup/model/PuzzleImporter.java @@ -13,6 +13,12 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; +/** + * Abstract class for importing puzzle data from various sources into a puzzle object. This class + * provides methods to initialize and set up a puzzle, including its board and proof structure, from + * different input formats such as dimensions, statements, or XML files. Subclasses must implement + * methods to handle specific formats for board initialization and proof creation. + */ public abstract class PuzzleImporter { private static final Logger LOGGER = LogManager.getLogger(PuzzleImporter.class.getName()); @@ -46,6 +52,14 @@ public void initializePuzzle(int rows, int columns) throws RuntimeException { } } + /** + * Initializes the puzzle with the given array of statements + * + * @param statements the statements used to initialize the puzzle + * @throws InputMismatchException if the input statements are invalid + * @throws IllegalArgumentException if the statements are not suitable for initializing the + * puzzle + */ public void initializePuzzle(String[] statements) throws InputMismatchException, IllegalArgumentException { // Note: Error checking for the statements will be left up to the puzzles that support @@ -55,10 +69,10 @@ public void initializePuzzle(String[] statements) } /** - * Initializes the puzzle attributes + * Initializes the puzzle attributes from the XML document node * - * @param node xml document node - * @throws InvalidFileFormatException if file is invalid + * @param node the XML document node representing the puzzle + * @throws InvalidFileFormatException if the file format is invalid */ public void initializePuzzle(Node node) throws InvalidFileFormatException { if (node.getNodeName().equalsIgnoreCase("puzzle")) { @@ -111,22 +125,30 @@ public void initializePuzzle(Node node) throws InvalidFileFormatException { } /** - * Creates the board for building + * Initializes the board with the specified number of rows and columns. * - * @param rows number of rows on the puzzle - * @param columns number of columns on the puzzle - * @throws RuntimeException if board can not be created + * @param rows the number of rows on the puzzle + * @param columns the number of columns on the puzzle + * @throws RuntimeException if the board cannot be created with the provided dimensions */ public abstract void initializeBoard(int rows, int columns); /** - * Creates an empty board for building + * Initializes the board from the XML document node. * - * @param node xml document node - * @throws InvalidFileFormatException if file is invalid + * @param node the XML document node representing the board + * @throws InvalidFileFormatException if the file format is invalid */ public abstract void initializeBoard(Node node) throws InvalidFileFormatException; + /** + * Initializes the board using an array of statements. + * + * @param statements the statements used to initialize the board + * @throws UnsupportedOperationException if the operation is not supported + * @throws IllegalArgumentException if the statements are not suitable for initializing the + * board + */ public abstract void initializeBoard(String[] statements) throws UnsupportedOperationException, IllegalArgumentException; @@ -368,6 +390,14 @@ protected void validateTreeStructure( } } + /** + * Updates the board state based on the changes specified in the TreeTransition. + * + * @param transition the TreeTransition object representing the transition to be updated + * @param transElement the XML node containing the transition data + * @throws InvalidFileFormatException if the XML node format is incorrect or unknown nodes are + * encountered + */ protected void makeTransitionChanges(TreeTransition transition, Node transElement) throws InvalidFileFormatException { if (transition.getRule() instanceof MergeRule) { @@ -411,6 +441,10 @@ protected void makeTransitionChanges(TreeTransition transition, Node transElemen } } + /** + * Creates a default proof tree with a single root node. The root node is initialized with the + * current board state. The created tree is then set as the proof tree for the puzzle. + */ protected void createDefaultTree() { TreeNode root = new TreeNode(puzzle.getCurrentBoard()); root.setRoot(true); @@ -420,7 +454,7 @@ protected void createDefaultTree() { } /** - * Gets the result of building the Puzzle + * Gets the result of building the Puzzle object. * * @return puzzle */ diff --git a/src/main/java/edu/rpi/legup/model/RegisterPuzzle.java b/src/main/java/edu/rpi/legup/model/RegisterPuzzle.java index c4c1ed273..df2a9bc5b 100644 --- a/src/main/java/edu/rpi/legup/model/RegisterPuzzle.java +++ b/src/main/java/edu/rpi/legup/model/RegisterPuzzle.java @@ -5,6 +5,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** Annotation for registering puzzle classes with the puzzle framework. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface RegisterPuzzle {} diff --git a/src/main/java/edu/rpi/legup/model/elements/Element.java b/src/main/java/edu/rpi/legup/model/elements/Element.java index 8b75d075d..ed47f6523 100644 --- a/src/main/java/edu/rpi/legup/model/elements/Element.java +++ b/src/main/java/edu/rpi/legup/model/elements/Element.java @@ -4,6 +4,10 @@ import java.awt.image.BufferedImage; import javax.swing.*; +/** + * The Element class serves as an abstract base class for various elements used in the system. It + * handles basic properties such as ID, name, description, and image associated with the element. + */ @RegisterElement public abstract class Element { protected String elementID; @@ -17,6 +21,14 @@ public abstract class Element { private final String INVALID_USE_MESSAGE; + /** + * Constructs an Element with the specified ID, name, description, and image name + * + * @param elementID Unique identifier for the element + * @param elementName Name of the element + * @param description Description of the element + * @param imageName File name of the image associated with the element + */ public Element(String elementID, String elementName, String description, String imageName) { this.elementID = elementID; this.elementName = elementName; @@ -26,6 +38,10 @@ public Element(String elementID, String elementName, String description, String loadImage(); } + /** + * Loads the image for the element and resizes it to a width of 100 pixels while maintaining + * aspect ratio + */ private void loadImage() { if (imageName != null) { this.image = new ImageIcon(ClassLoader.getSystemClassLoader().getResource(imageName)); @@ -47,30 +63,65 @@ private void loadImage() { } } + /** + * Gets the name of the element + * + * @return The name of the element + */ public String getElementName() { return elementName; } + /** + * Sets the name of the element + * + * @param elementName The new name for the element + */ public void setElementName(String elementName) { this.elementName = elementName; } + /** + * Gets the unique identifier of the element + * + * @return The ID of the element + */ public String getElementID() { return elementID; } + /** + * Gets the description of the element + * + * @return The description of the element + */ public String getDescription() { return description; } + /** + * Gets the image icon associated with the element + * + * @return The ImageIcon for the element + */ public ImageIcon getImageIcon() { return image; } + /** + * Gets the type of the element + * + * @return The ElementType of the element + */ public ElementType getElementType() { return elementType; } + /** + * Gets the message for invalid use of the rule + * + * @return The invalid use message + */ public String getInvalidUseOfRuleMessage() { return this.INVALID_USE_MESSAGE; } diff --git a/src/main/java/edu/rpi/legup/model/elements/ElementType.java b/src/main/java/edu/rpi/legup/model/elements/ElementType.java index dff4fe04f..142427efe 100644 --- a/src/main/java/edu/rpi/legup/model/elements/ElementType.java +++ b/src/main/java/edu/rpi/legup/model/elements/ElementType.java @@ -1,6 +1,7 @@ package edu.rpi.legup.model.elements; +/** Enum representing the different types of elements that can be used in the system */ public enum ElementType { - PLACEABLE, - NONPLACEABLE + PLACEABLE + // NONPLACEABLE COMBINED ALL PLACEABLE AND NONPLACEABLE INTO JUST ONE CATEGORY } diff --git a/src/main/java/edu/rpi/legup/model/elements/NonPlaceableElement.java b/src/main/java/edu/rpi/legup/model/elements/NonPlaceableElement.java index 4ab0ab509..5c4e96556 100644 --- a/src/main/java/edu/rpi/legup/model/elements/NonPlaceableElement.java +++ b/src/main/java/edu/rpi/legup/model/elements/NonPlaceableElement.java @@ -1,9 +1,9 @@ -package edu.rpi.legup.model.elements; - -public abstract class NonPlaceableElement extends Element { - public NonPlaceableElement( - String elementID, String elementName, String description, String imageName) { - super(elementID, elementName, description, imageName); - this.elementType = ElementType.NONPLACEABLE; - } -} +// package edu.rpi.legup.model.elements; +// +// public abstract class NonPlaceableElement extends Element { +// public NonPlaceableElement( +// String elementID, String elementName, String description, String imageName) { +// super(elementID, elementName, description, imageName); +// this.elementType = ElementType.PLACEABLE; +// } +// } diff --git a/src/main/java/edu/rpi/legup/model/elements/PlaceableElement.java b/src/main/java/edu/rpi/legup/model/elements/PlaceableElement.java index 133658700..820dc08dd 100644 --- a/src/main/java/edu/rpi/legup/model/elements/PlaceableElement.java +++ b/src/main/java/edu/rpi/legup/model/elements/PlaceableElement.java @@ -1,6 +1,19 @@ package edu.rpi.legup.model.elements; +/** + * Abstract class representing elements that can be placed within the system. Inherits from the + * {@link Element} class and sets the {@link ElementType} to {@link ElementType#PLACEABLE}. + */ public abstract class PlaceableElement extends Element { + + /** + * Constructs a PlaceableElement with the specified details + * + * @param elementID Unique identifier for the element + * @param elementName Name of the element + * @param description Description of the element + * @param imageName Name of the image file representing the element + */ public PlaceableElement( String elementID, String elementName, String description, String imageName) { super(elementID, elementName, description, imageName); diff --git a/src/main/java/edu/rpi/legup/model/elements/RegisterElement.java b/src/main/java/edu/rpi/legup/model/elements/RegisterElement.java index 368ecc8d1..916719ebc 100644 --- a/src/main/java/edu/rpi/legup/model/elements/RegisterElement.java +++ b/src/main/java/edu/rpi/legup/model/elements/RegisterElement.java @@ -3,6 +3,11 @@ import java.lang.annotation.*; import java.lang.annotation.ElementType; +/** + * Annotation to mark classes as elements that should be registered. This annotation is used to + * indicate that a class is an element within the system and should be registered for use within the + * application. + */ @Inherited @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/edu/rpi/legup/model/gameboard/Board.java b/src/main/java/edu/rpi/legup/model/gameboard/Board.java index d8bdf5199..9e7d93441 100644 --- a/src/main/java/edu/rpi/legup/model/gameboard/Board.java +++ b/src/main/java/edu/rpi/legup/model/gameboard/Board.java @@ -5,6 +5,10 @@ import java.util.List; import java.util.Set; +/** + * Abstract class representing a game board. This class provides functionality for managing puzzle + * elements, tracking modifications, and determining if the board is modifiable. + */ public abstract class Board { protected List puzzleElements; @@ -31,21 +35,24 @@ public Board(int size) { } /** - * Gets a specific {@link PuzzleElement} on this board. + * Gets a specific {@link PuzzleElement} from the board. * - * @param puzzleElement equivalent puzzleElement - * @return equivalent puzzleElement on this board + * @param puzzleElement the puzzle element to retrieve + * @return the puzzle element at the corresponding index, or null if not found */ public PuzzleElement getPuzzleElement(PuzzleElement puzzleElement) { + if (puzzleElement == null) { + return null; + } int index = puzzleElement.getIndex(); return index < puzzleElements.size() ? puzzleElements.get(index) : null; } /** - * Sets a specific {@link PuzzleElement} on the board. + * Sets a specific {@link PuzzleElement} on the board * * @param index index of the puzzleElement - * @param puzzleElement new puzzleElement at the index + * @param puzzleElement the puzzleElement to set at the index */ public void setPuzzleElement(int index, PuzzleElement puzzleElement) { if (index < puzzleElements.size()) { @@ -54,7 +61,7 @@ public void setPuzzleElement(int index, PuzzleElement puzzleElement) { } /** - * Gets the number of elements on the board. + * Gets the number of elements on the board * * @return number of elements on the board */ diff --git a/src/main/java/edu/rpi/legup/model/gameboard/CaseBoard.java b/src/main/java/edu/rpi/legup/model/gameboard/CaseBoard.java index fa3625a43..07cc2947d 100644 --- a/src/main/java/edu/rpi/legup/model/gameboard/CaseBoard.java +++ b/src/main/java/edu/rpi/legup/model/gameboard/CaseBoard.java @@ -5,49 +5,106 @@ import java.util.HashSet; import java.util.Set; +/** + * Represents a game board with specific rules for selecting puzzle elements. Extends the abstract + * `Board` class and adds functionality for handling pickable elements and case rules. + */ public class CaseBoard extends Board { protected Board baseBoard; protected CaseRule caseRule; protected Set pickablePuzzleElements; + /** + * Constructs a CaseBoard with a base board and a case rule. + * + * @param baseBoard the base board to use for this CaseBoard + * @param caseRule the case rule applied to this CaseBoard + */ public CaseBoard(Board baseBoard, CaseRule caseRule) { this.baseBoard = baseBoard; this.caseRule = caseRule; this.pickablePuzzleElements = new HashSet<>(); } + /** + * Adds a puzzle element to the set of pickable elements. + * + * @param puzzleElement the puzzle element to add + */ public void addPickableElement(PuzzleElement puzzleElement) { pickablePuzzleElements.add(puzzleElement); } + /** + * Removes a puzzle element from the set of pickable elements. + * + * @param puzzleElement the puzzle element to remove + */ public void removePickableElement(PuzzleElement puzzleElement) { pickablePuzzleElements.remove(puzzleElement); } + /** + * Checks if a puzzle element is pickable based on the mouse event. + * + * @param puzzleElement the puzzle element to check + * @param e the mouse event + * @return true if the puzzle element is pickable, false otherwise + */ public boolean isPickable(PuzzleElement puzzleElement, MouseEvent e) { return pickablePuzzleElements.contains(baseBoard.getPuzzleElement(puzzleElement)); } + /** + * Retrieves the base board for this CaseBoard. + * + * @return the base board + */ public Board getBaseBoard() { return baseBoard; } + /** + * Sets the base board for this CaseBoard. + * + * @param baseBoard the new base board + */ public void setBaseBoard(Board baseBoard) { this.baseBoard = baseBoard; } + /** + * Retrieves the case rule for this CaseBoard. + * + * @return the case rule + */ public CaseRule getCaseRule() { return caseRule; } + /** + * Sets the case rule for this CaseBoard. + * + * @param caseRule the new case rule + */ public void setCaseRule(CaseRule caseRule) { this.caseRule = caseRule; } + /** + * Gets the count of pickable puzzle elements. + * + * @return the number of pickable elements + */ public int getCount() { return pickablePuzzleElements.size(); } + /** + * Performs a deep copy of this CaseBoard. CURRENTLY NOT IMPLEMENTED AND RETURNS NULL + * + * @return a new copy of the CaseBoard + */ public CaseBoard copy() { return null; } diff --git a/src/main/java/edu/rpi/legup/model/gameboard/ElementFactory.java b/src/main/java/edu/rpi/legup/model/gameboard/ElementFactory.java index bfc785bdd..131feb122 100644 --- a/src/main/java/edu/rpi/legup/model/gameboard/ElementFactory.java +++ b/src/main/java/edu/rpi/legup/model/gameboard/ElementFactory.java @@ -5,6 +5,9 @@ import org.w3c.dom.Element; import org.w3c.dom.Node; +/** + * ElementFactory is an abstract class for importing and exporting {@link PuzzleElement} instances. + */ public abstract class ElementFactory { /** diff --git a/src/main/java/edu/rpi/legup/model/gameboard/GridBoard.java b/src/main/java/edu/rpi/legup/model/gameboard/GridBoard.java index 9593690ce..7338132f8 100644 --- a/src/main/java/edu/rpi/legup/model/gameboard/GridBoard.java +++ b/src/main/java/edu/rpi/legup/model/gameboard/GridBoard.java @@ -6,6 +6,11 @@ import java.awt.*; import java.awt.event.MouseEvent; +/** + * GridBoard represents a grid-based board where each cell can be manipulated based on its + * coordinates. The board supports operations such as getting and setting cells, and provides + * dimensions of the grid. It also supports deep copying of the board. + */ public class GridBoard extends Board { protected Dimension dimension; diff --git a/src/main/java/edu/rpi/legup/model/gameboard/GridCell.java b/src/main/java/edu/rpi/legup/model/gameboard/GridCell.java index a33c3ec80..671b89e9b 100644 --- a/src/main/java/edu/rpi/legup/model/gameboard/GridCell.java +++ b/src/main/java/edu/rpi/legup/model/gameboard/GridCell.java @@ -2,6 +2,13 @@ import java.awt.*; +/** + * GridCell represents a cell within a grid-based board. It holds data of type T and tracks its + * location on the board using a {@link Point}. The class extends from PuzzleElement and supports + * deep copying of the grid cell. + * + * @param the type of data held by the GridCell + */ public class GridCell extends PuzzleElement { protected Point location; diff --git a/src/main/java/edu/rpi/legup/model/gameboard/GridRegion.java b/src/main/java/edu/rpi/legup/model/gameboard/GridRegion.java index c41d5e1b2..79027d9d1 100644 --- a/src/main/java/edu/rpi/legup/model/gameboard/GridRegion.java +++ b/src/main/java/edu/rpi/legup/model/gameboard/GridRegion.java @@ -3,6 +3,12 @@ import java.util.ArrayList; import java.util.List; +/** + * GridRegion represents a collection of cells within a grid. It manages a list of cells and + * provides methods to add, remove, and retrieve cells from the region. + * + * @param the type of cell managed by the GridRegion + */ public abstract class GridRegion { protected List regionCells; @@ -24,7 +30,7 @@ public void addCell(T cell) { /** * Removes the cell from the region * - * @param cell cell to be remove from the region + * @param cell cell to be removed from the region */ public void removeCell(T cell) { regionCells.remove(cell); @@ -47,9 +53,4 @@ public List getCells() { public int getSize() { return regionCells.size(); } - - /* - public void colorRegion(){} - */ - } diff --git a/src/main/java/edu/rpi/legup/model/gameboard/PuzzleElement.java b/src/main/java/edu/rpi/legup/model/gameboard/PuzzleElement.java index 4ce030a04..eca6f3be4 100644 --- a/src/main/java/edu/rpi/legup/model/gameboard/PuzzleElement.java +++ b/src/main/java/edu/rpi/legup/model/gameboard/PuzzleElement.java @@ -3,11 +3,19 @@ import edu.rpi.legup.model.elements.Element; import java.awt.event.MouseEvent; +/** + * PuzzleElement represents a single element in a puzzle grid. It holds data and provides various + * methods to manage and retrieve its properties, including modifiability, modification status, and + * validity. + * + * @param the type of data held by the PuzzleElement + */ public abstract class PuzzleElement { protected int index; protected T data; protected boolean isModifiable; protected boolean isModified; + protected boolean isModifiableCaseRule; protected boolean isGiven; protected boolean isValid; protected int casesDepended; @@ -17,6 +25,7 @@ public PuzzleElement() { this.index = -1; this.data = null; this.isModifiable = true; + this.isModifiableCaseRule = true; this.isModified = false; this.isGiven = false; this.isValid = true; @@ -73,6 +82,24 @@ public void setModifiable(boolean isModifiable) { this.isModifiable = isModifiable; } + /** + * Gets whether this puzzle element is modifiable as a result of a case rule. + * + * @return true if this puzzle element is modifiable, false otherwise + */ + public boolean isModifiableCaseRule() { + return isModifiableCaseRule; + } + + /** + * Sets whether this puzzle element is modifiable as a result of a case rule. + * + * @param isModifiableCaseRule true if this puzzle element is modifiable, false otherwise + */ + public void setModifiableCaseRule(boolean isModifiableCaseRule) { + this.isModifiableCaseRule = isModifiableCaseRule; + } + /** * Gets whether the puzzle element has been modified. * diff --git a/src/main/java/edu/rpi/legup/model/observer/IBoardListener.java b/src/main/java/edu/rpi/legup/model/observer/IBoardListener.java index 461128562..7ba886fd4 100644 --- a/src/main/java/edu/rpi/legup/model/observer/IBoardListener.java +++ b/src/main/java/edu/rpi/legup/model/observer/IBoardListener.java @@ -4,6 +4,10 @@ import edu.rpi.legup.model.gameboard.PuzzleElement; import edu.rpi.legup.model.tree.TreeElement; +/** + * IBoardListener defines methods for receiving notifications about changes to the board, including + * updates to tree elements, case boards, and puzzle elements. + */ public interface IBoardListener { /** * Called when the tree element has changed. @@ -13,7 +17,7 @@ public interface IBoardListener { void onTreeElementChanged(TreeElement treeElement); /** - * Called when the a case board has been added to the view. + * Called when a case board has been added to the view. * * @param caseBoard case board to be added */ diff --git a/src/main/java/edu/rpi/legup/model/observer/IBoardSubject.java b/src/main/java/edu/rpi/legup/model/observer/IBoardSubject.java index c7bf13141..6cdcd7dc2 100644 --- a/src/main/java/edu/rpi/legup/model/observer/IBoardSubject.java +++ b/src/main/java/edu/rpi/legup/model/observer/IBoardSubject.java @@ -2,6 +2,10 @@ import java.util.function.Consumer; +/** + * IBoardSubject defines methods for managing and notifying board listeners. It allows for adding + * and removing listeners, and for notifying them using a specified algorithm. + */ public interface IBoardSubject { /** * Adds a board listener. @@ -18,7 +22,7 @@ public interface IBoardSubject { void removeBoardListener(IBoardListener listener); /** - * Notifies all of the listeners using the specified algorithm. + * Notifies all the listeners using the specified algorithm. * * @param algorithm algorithm used to notify the listeners */ diff --git a/src/main/java/edu/rpi/legup/model/observer/ITreeListener.java b/src/main/java/edu/rpi/legup/model/observer/ITreeListener.java index d5e7fdb2d..713b464e2 100644 --- a/src/main/java/edu/rpi/legup/model/observer/ITreeListener.java +++ b/src/main/java/edu/rpi/legup/model/observer/ITreeListener.java @@ -3,6 +3,10 @@ import edu.rpi.legup.model.tree.TreeElement; import edu.rpi.legup.ui.proofeditorui.treeview.TreeViewSelection; +/** + * ITreeListener defines methods for handling events related to changes in the tree. Implementations + * of this interface are notified of additions, removals, and selection changes in the tree. + */ public interface ITreeListener { /** * Called when a {@link TreeElement} is added to the tree. diff --git a/src/main/java/edu/rpi/legup/model/observer/ITreeSubject.java b/src/main/java/edu/rpi/legup/model/observer/ITreeSubject.java index 66d5d9a5e..f7c033529 100644 --- a/src/main/java/edu/rpi/legup/model/observer/ITreeSubject.java +++ b/src/main/java/edu/rpi/legup/model/observer/ITreeSubject.java @@ -2,9 +2,13 @@ import java.util.function.Consumer; +/** + * ITreeSubject defines methods for managing and notifying listeners about changes to the tree + * model. Implementations of this interface handle adding, removing, and notifying listeners. + */ public interface ITreeSubject { /** - * Adds a board listener. + * Adds a tree listener. * * @param listener listener to add */ @@ -18,7 +22,7 @@ public interface ITreeSubject { void removeTreeListener(ITreeListener listener); /** - * Notifies all of the listeners using the specified algorithm. + * Notifies all the tree listeners using the specified algorithm. * * @param algorithm algorithm used to notify the listeners */ diff --git a/src/main/java/edu/rpi/legup/model/rules/CaseRule.java b/src/main/java/edu/rpi/legup/model/rules/CaseRule.java index ab2cc8f0d..aac4fd352 100644 --- a/src/main/java/edu/rpi/legup/model/rules/CaseRule.java +++ b/src/main/java/edu/rpi/legup/model/rules/CaseRule.java @@ -11,6 +11,11 @@ import java.util.List; import java.util.Set; +/** + * CaseRule is an abstract class representing a rule that can be applied with multiple cases in a + * puzzle board. It defines methods for applying and checking the rule, as well as retrieving the + * necessary elements. + */ public abstract class CaseRule extends Rule { private final String INVALID_USE_MESSAGE; diff --git a/src/main/java/edu/rpi/legup/model/rules/ContradictionRule.java b/src/main/java/edu/rpi/legup/model/rules/ContradictionRule.java index b38a95fd2..c80a551ec 100644 --- a/src/main/java/edu/rpi/legup/model/rules/ContradictionRule.java +++ b/src/main/java/edu/rpi/legup/model/rules/ContradictionRule.java @@ -6,6 +6,11 @@ import edu.rpi.legup.model.gameboard.PuzzleElement; import edu.rpi.legup.model.tree.TreeTransition; +/** + * ContradictionRule is an abstract class representing a rule that identifies contradictions in a + * puzzle. It provides methods to check for contradictions both globally and at specific puzzle + * elements. + */ public abstract class ContradictionRule extends Rule { private final String NO_CONTRADICTION_MESSAGE = diff --git a/src/main/java/edu/rpi/legup/model/rules/DirectRule.java b/src/main/java/edu/rpi/legup/model/rules/DirectRule.java index d550bc02c..8efe119b6 100644 --- a/src/main/java/edu/rpi/legup/model/rules/DirectRule.java +++ b/src/main/java/edu/rpi/legup/model/rules/DirectRule.java @@ -7,6 +7,10 @@ import edu.rpi.legup.model.tree.TreeNode; import edu.rpi.legup.model.tree.TreeTransition; +/** + * DirectRule is an abstract class representing a direct rule for transitions in a puzzle. It + * provides methods for checking whether transitions and specific puzzle elements follow the rule. + */ public abstract class DirectRule extends Rule { /** * DirectRule Constructor creates a new basic rule. diff --git a/src/main/java/edu/rpi/legup/model/rules/MergeRule.java b/src/main/java/edu/rpi/legup/model/rules/MergeRule.java index f7badcd8b..86c63fb7c 100644 --- a/src/main/java/edu/rpi/legup/model/rules/MergeRule.java +++ b/src/main/java/edu/rpi/legup/model/rules/MergeRule.java @@ -10,6 +10,10 @@ import java.util.ArrayList; import java.util.List; +/** + * MergeRule is an implementation of a rule that merges multiple nodes into one. It validates if the + * merging of nodes is done correctly. + */ public class MergeRule extends Rule { /** MergeRule Constructor merges to board states together */ public MergeRule() { @@ -23,7 +27,7 @@ public MergeRule() { /** * Checks whether the transition logically follows from the parent node using this rule. This - * method is the one that should overridden in child classes + * method is the one that should have overridden in child classes * * @param transition transition to check * @return null if the child node logically follow from the parent node, otherwise error message diff --git a/src/main/java/edu/rpi/legup/model/rules/RegisterRule.java b/src/main/java/edu/rpi/legup/model/rules/RegisterRule.java index c1fe0b88c..0ee2a686e 100644 --- a/src/main/java/edu/rpi/legup/model/rules/RegisterRule.java +++ b/src/main/java/edu/rpi/legup/model/rules/RegisterRule.java @@ -2,6 +2,10 @@ import java.lang.annotation.*; +/** + * Annotation to register a class as a rule in the system. This annotation can be applied to rule + * classes to indicate that they should be registered in the rule system. + */ @Inherited @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/edu/rpi/legup/model/rules/Rule.java b/src/main/java/edu/rpi/legup/model/rules/Rule.java index f70bb2889..edcab550b 100644 --- a/src/main/java/edu/rpi/legup/model/rules/Rule.java +++ b/src/main/java/edu/rpi/legup/model/rules/Rule.java @@ -8,6 +8,11 @@ import java.awt.image.BufferedImage; import javax.swing.ImageIcon; +/** + * Abstract base class for defining rules. This class encapsulates the common functionality and + * attributes of all rules, including rule identification, description, image handling, and + * validation logic. Subclasses must provide implementations for specific rule checking logic. + */ @RegisterRule public abstract class Rule { protected String ruleID; @@ -159,6 +164,11 @@ public RuleType getRuleType() { return ruleType; } + /** + * Gets the message indicating an invalid use of the rule. + * + * @return the invalid use message + */ public String getInvalidUseOfRuleMessage() { return this.INVALID_USE_MESSAGE; } diff --git a/src/main/java/edu/rpi/legup/model/rules/RuleType.java b/src/main/java/edu/rpi/legup/model/rules/RuleType.java index 06aa1844b..7eee80ea3 100644 --- a/src/main/java/edu/rpi/legup/model/rules/RuleType.java +++ b/src/main/java/edu/rpi/legup/model/rules/RuleType.java @@ -1,5 +1,9 @@ package edu.rpi.legup.model.rules; +/** + * Enumeration representing different types of rules in the rule-based system. This enum categorizes + * rules into various types based on their functionality and application. + */ public enum RuleType { BASIC, CASE, diff --git a/src/main/java/edu/rpi/legup/model/tree/Tree.java b/src/main/java/edu/rpi/legup/model/tree/Tree.java index a0746db87..545976fd1 100644 --- a/src/main/java/edu/rpi/legup/model/tree/Tree.java +++ b/src/main/java/edu/rpi/legup/model/tree/Tree.java @@ -1,11 +1,17 @@ package edu.rpi.legup.model.tree; +import edu.rpi.legup.controller.TreeController; import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.ui.proofeditorui.treeview.TreeView; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +/** + * Represents a tree structure in a puzzle. The tree consists of {@link TreeNode}s and {@link + * TreeTransition}s and allows adding, removing, and validating elements. + */ public class Tree { private TreeNode rootNode; @@ -24,6 +30,12 @@ public Tree() { this.rootNode = null; } + /** + * Adds a new transition to the specified node. + * + * @param treeNode the node to add a transition to + * @return the created transition + */ public TreeTransition addNewTransition(TreeNode treeNode) { TreeTransition transition = new TreeTransition(treeNode, treeNode.getBoard().copy()); treeNode.addChild(transition); @@ -31,13 +43,12 @@ public TreeTransition addNewTransition(TreeNode treeNode) { return transition; } - public TreeNode addNode(TreeTransition transition) { - TreeNode treeNode = new TreeNode(transition.getBoard().copy()); - transition.setChildNode(treeNode); - treeNode.setParent(transition); - return treeNode; - } - + /** + * Adds a tree element (node or transition) to the tree. + * + * @param element the tree element to add + * @return the added tree element + */ public TreeElement addTreeElement(TreeElement element) { if (element.getType() == TreeElementType.NODE) { TreeNode treeNode = (TreeNode) element; @@ -46,30 +57,55 @@ public TreeElement addTreeElement(TreeElement element) { } else { TreeTransition transition = (TreeTransition) element; Board copyBoard = transition.board.copy(); - copyBoard.setModifiable(false); + copyBoard.setModifiable(true); return addTreeElement(transition, new TreeNode(copyBoard)); } } + /** + * Adds a tree node and its associated transition to the tree. + * + * @param treeNode the tree node to add + * @param transition the transition to associate with the node + * @return the added transition + */ public TreeElement addTreeElement(TreeNode treeNode, TreeTransition transition) { treeNode.addChild(transition); treeNode.getChildren().forEach(TreeTransition::reverify); return transition; } + /** + * Adds a transition and its associated tree node to the tree. + * + * @param transition the transition to add + * @param treeNode the tree node to associate with the transition + * @return the added tree node + */ public TreeElement addTreeElement(TreeTransition transition, TreeNode treeNode) { transition.setChildNode(treeNode); treeNode.setParent(transition); return treeNode; } + /** + * Removes a tree element (node or transition) from the tree. + * + * @param element the tree element to remove + */ public void removeTreeElement(TreeElement element) { if (element.getType() == TreeElementType.NODE) { TreeNode node = (TreeNode) element; + + node.getParent().removeChild(node); node.getParent().setChildNode(null); } else { TreeTransition transition = (TreeTransition) element; + transition.getParents().forEach(n -> n.removeChild(transition)); + TreeController treeController = new TreeController(); + TreeView treeView = new TreeView(treeController); + treeView.removeTreeTransition(transition); transition.getParents().get(0).getChildren().forEach(TreeTransition::reverify); } } @@ -96,10 +132,10 @@ public Set getLeafTreeElements() { } /** - * Gets a Set of TreeNodes that are leaf nodes from the sub tree rooted at the specified node + * Gets a Set of TreeNodes that are leaf nodes from the subtree rooted at the specified node * * @param node node that is input - * @return Set of TreeNodes that are leaf nodes from the sub tree + * @return Set of TreeNodes that are leaf nodes from the subtree */ public Set getLeafTreeElements(TreeNode node) { Set leafs = new HashSet<>(); diff --git a/src/main/java/edu/rpi/legup/model/tree/TreeElement.java b/src/main/java/edu/rpi/legup/model/tree/TreeElement.java index 59f75acf3..8c865b89b 100644 --- a/src/main/java/edu/rpi/legup/model/tree/TreeElement.java +++ b/src/main/java/edu/rpi/legup/model/tree/TreeElement.java @@ -2,6 +2,10 @@ import edu.rpi.legup.model.gameboard.Board; +/** + * Represents an element in a tree structure, which can be either a {@link TreeNode} or a {@link + * TreeTransition}. + */ public abstract class TreeElement { protected TreeElementType type; protected Board board; @@ -24,7 +28,7 @@ public TreeElement(TreeElementType type) { public abstract boolean isContradictoryBranch(); /** - * Recursively determines if the sub-tree rooted at this tree puzzleElement is valid by checking + * Recursively determines if the subtree rooted at this tree puzzleElement is valid by checking * whether this tree puzzleElement and all descendants of this tree puzzleElement is justified * and justified correctly * diff --git a/src/main/java/edu/rpi/legup/model/tree/TreeElementType.java b/src/main/java/edu/rpi/legup/model/tree/TreeElementType.java index 67437a535..eb585329e 100644 --- a/src/main/java/edu/rpi/legup/model/tree/TreeElementType.java +++ b/src/main/java/edu/rpi/legup/model/tree/TreeElementType.java @@ -1,5 +1,9 @@ package edu.rpi.legup.model.tree; +/** + * Enum representing the type of a tree element. Tree elements can be either nodes or transitions in + * the tree structure. + */ public enum TreeElementType { NODE, TRANSITION diff --git a/src/main/java/edu/rpi/legup/model/tree/TreeNode.java b/src/main/java/edu/rpi/legup/model/tree/TreeNode.java index a2ac7cb21..d4095afa5 100644 --- a/src/main/java/edu/rpi/legup/model/tree/TreeNode.java +++ b/src/main/java/edu/rpi/legup/model/tree/TreeNode.java @@ -4,6 +4,11 @@ import edu.rpi.legup.utility.DisjointSets; import java.util.*; +/** + * Represents a node in a tree structure. Extends {@link TreeElement}. A {@code TreeNode} contains a + * board, references to its parent and children transitions, and indicates if it is the root node of + * the tree. + */ public class TreeNode extends TreeElement { private TreeTransition parent; private List children; @@ -56,9 +61,9 @@ public boolean isValidBranch() { } /** - * Gets all of the ancestors of this node + * Gets a list of the ancestors of this node * - * @return list of all of the ancestors for this node + * @return list of all the ancestors for this node */ public List getAncestors() { List ancestors = new ArrayList<>(); @@ -326,6 +331,10 @@ public void setRoot(boolean isRoot) { this.isRoot = isRoot; } + /** + * Clears all children transitions from this tree node. After calling this method, the node will + * have no child transitions. + */ public void clearChildren() { this.children.clear(); } diff --git a/src/main/java/edu/rpi/legup/model/tree/TreeTransition.java b/src/main/java/edu/rpi/legup/model/tree/TreeTransition.java index e79cd4b96..10c8fc7a0 100644 --- a/src/main/java/edu/rpi/legup/model/tree/TreeTransition.java +++ b/src/main/java/edu/rpi/legup/model/tree/TreeTransition.java @@ -8,6 +8,11 @@ import java.util.ArrayList; import java.util.List; +/** + * Represents a transition between two nodes in a tree structure within a game. A transition is + * responsible for propagating changes through the tree and managing and verifying the associated + * rules and puzzle elements. + */ public class TreeTransition extends TreeElement { private ArrayList parents; private TreeNode childNode; @@ -332,6 +337,24 @@ public void setChildNode(TreeNode childNode) { this.childNode = childNode; } + /** + * Removes the child to this tree transition + * + * @param child child to remove + */ + public void removeChild(TreeNode child) { + parents.remove(child); + } + + /** + * Add the child to this tree transition + * + * @param child child to add + */ + public void addChild(TreeNode child) { + parents.add(child); + } + /** * Gets the rule associated with this transition * diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/Binary.java b/src/main/java/edu/rpi/legup/puzzle/binary/Binary.java index 773513cda..7b70a43a1 100644 --- a/src/main/java/edu/rpi/legup/puzzle/binary/Binary.java +++ b/src/main/java/edu/rpi/legup/puzzle/binary/Binary.java @@ -36,18 +36,12 @@ public Board generatePuzzle(int difficulty) { return null; } - // /** - // * Determines if the given dimensions are valid for Binary - // * - // * @param rows the number of rows - // * @param columns the number of columns - // * @return true if the given dimensions are valid for Binary, false otherwise - // */ - // @Override - // public boolean isValidDimensions(int rows, int columns){ - // return rows >= 2 && rows % 2 == 0 && columns >= 2 && columns % 2 == 0; - // } - + /** + * Determines if the current board is a valid state + * + * @param board board to check for validity + * @return true if board is valid, false otherwise + */ @Override public boolean isBoardComplete(Board board) { BinaryBoard binaryBoard = (BinaryBoard) board; @@ -66,6 +60,23 @@ public boolean isBoardComplete(Board board) { return true; } + /** + * Callback for when the board puzzleElement changes + * + * @param board the board that has changed + */ @Override public void onBoardChange(Board board) {} + + /** + * Determines if the given dimensions are valid for Binary + * + * @param rows the number of rows + * @param columns the number of columns + * @return true if the given dimensions are valid for Binary, false otherwise + */ + @Override + public boolean isValidDimensions(int rows, int columns) { + return rows >= 2 && rows % 2 == 0 && rows == columns; + } } diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryBoard.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryBoard.java index 35c37b1a1..d5d65ed26 100644 --- a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryBoard.java +++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryBoard.java @@ -2,7 +2,6 @@ import edu.rpi.legup.model.gameboard.GridBoard; import edu.rpi.legup.model.gameboard.PuzzleElement; -import java.awt.*; import java.util.ArrayList; import java.util.HashSet; import java.util.Set; @@ -20,6 +19,13 @@ public BinaryBoard(int size) { this.size = size; } + /** + * Gets the cell at the (x,y) position + * + * @param x x-coordinate + * @param y y-coordinate + * @return BinaryCell cell at (x,y) + */ @Override public BinaryCell getCell(int x, int y) { if (y * dimension.width + x >= puzzleElements.size() @@ -32,6 +38,12 @@ public BinaryCell getCell(int x, int y) { return (BinaryCell) super.getCell(x, y); } + /** + * Get all the binary cells in a row + * + * @param rowNum row number + * @return set of all binary cells in specified rowNum + */ public Set getRowCells(int rowNum) { Set row = new HashSet<>(); for (int i = 0; i < size; i++) { @@ -41,6 +53,26 @@ public Set getRowCells(int rowNum) { return row; } + /** + * Get all the binary cells in a column + * + * @param colNum column number + * @return set of all binary cells in specified colNum + */ + public Set getColCells(int colNum) { + Set col = new HashSet<>(); + for (int i = 0; i < size; i++) { + col.add(getCell(colNum, i)); + } + return col; + } + + /** + * Get all the binary types in a row + * + * @param rowNum row number + * @return ArrayList of all binary types in specified rowNum + */ public ArrayList getRowTypes(int rowNum) { ArrayList row = new ArrayList(); for (int i = 0; i < size; i++) { @@ -50,6 +82,12 @@ public ArrayList getRowTypes(int rowNum) { return row; } + /** + * Get all the binary types in a column + * + * @param colNum column number + * @return ArrayList of all binary types in specified colNum + */ public ArrayList getColTypes(int colNum) { ArrayList col = new ArrayList(); for (int i = 0; i < size; i++) { @@ -59,17 +97,13 @@ public ArrayList getColTypes(int colNum) { return col; } - public Set getCol(int colNum) { - Set col = new HashSet<>(); - for (int i = 0; i < size; i++) { - col.add(getCell(colNum, i)); - } - return col; - } - + /** + * Get a copy of the binary board + * + * @return copy of current BinaryBoard + */ @Override public BinaryBoard copy() { - System.out.println("BinaryBoard copy()"); BinaryBoard copy = new BinaryBoard(dimension.width, dimension.height); for (int x = 0; x < this.dimension.width; x++) { for (int y = 0; y < this.dimension.height; y++) { diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryCell.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryCell.java index 9007215ad..0edb8444e 100644 --- a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryCell.java +++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryCell.java @@ -1,13 +1,26 @@ package edu.rpi.legup.puzzle.binary; +import edu.rpi.legup.model.elements.Element; import edu.rpi.legup.model.gameboard.GridCell; import java.awt.Point; +import java.awt.event.MouseEvent; public class BinaryCell extends GridCell { - public BinaryCell(int valueInt, Point location) { - super(valueInt, location); + /** + * BinaryCell Constructor - creates a BinaryCell from the specified value and location + * + * @param value value of the BinaryCell + * @param location position of the BinaryCell + */ + public BinaryCell(int value, Point location) { + super(value, location); } + /** + * Gets the type of this BinaryCell + * + * @return type of BinaryCell + */ public BinaryType getType() { switch (data) { case 0: @@ -24,6 +37,11 @@ public BinaryType getType() { return null; } + /** + * Performs a deep copy on the BinaryCell + * + * @return a new copy of the BinaryCell that is independent of this one + */ @Override public BinaryCell copy() { BinaryCell copy = new BinaryCell(data, (Point) location.clone()); @@ -32,4 +50,32 @@ public BinaryCell copy() { copy.setGiven(isGiven); return copy; } + + /** + * Sets the type of this BinaryCell + * + * @param e element to set the type of this binary cell to + */ + @Override + public void setType(Element e, MouseEvent m) { + if (e.getElementName().equals("Number Tile")) { + if (m.getButton() == MouseEvent.BUTTON1) { + if (this.data == 2) { + this.data = 0; + } else { + this.data = this.data + 1; + } + } else { + if (m.getButton() == MouseEvent.BUTTON3) { + if (this.data > 0) { + this.data = this.data - 1; + } else { + this.data = 2; + } + } + } + } else { // unknown tile + this.data = 2; + } + } } diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryCellFactory.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryCellFactory.java index 890c26656..a819177d6 100644 --- a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryCellFactory.java +++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryCellFactory.java @@ -10,7 +10,14 @@ import org.w3c.dom.Node; public class BinaryCellFactory extends ElementFactory { - + /** + * Creates a puzzleElement based on the xml document Node and adds it to the board + * + * @param node node that represents the puzzleElement + * @param board board to add the newly created cell + * @return newly created cell from the xml document Node + * @throws InvalidFileFormatException if file is invalid + */ public BinaryCell importCell(Node node, Board board) throws InvalidFileFormatException { try { if (!node.getNodeName().equalsIgnoreCase("cell")) { @@ -45,6 +52,13 @@ public BinaryCell importCell(Node node, Board board) throws InvalidFileFormatExc } } + /** + * Creates a xml document puzzleElement from a cell for exporting + * + * @param document xml document + * @param puzzleElement PuzzleElement cell + * @return xml PuzzleElement + */ public org.w3c.dom.Element exportCell(Document document, PuzzleElement puzzleElement) { org.w3c.dom.Element cellElement = document.createElement("cell"); diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryController.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryController.java index 0bad559d9..5ec6669a0 100644 --- a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryController.java +++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryController.java @@ -6,6 +6,15 @@ public class BinaryController extends ElementController { + /** + * Handles cell state changes in the binary puzzle when a mouse event occurs If the left mouse + * button is clicked: - If the control key is held down, shows a context menu at the mouse + * position - Otherwise, toggles the cell data state between 0, 1, and 2 in a cyclic manner If + * the right mouse button is clicked, the cell state is also toggled between 2, 1, and 0 + * + * @param e MouseEvent triggered by the user interaction + * @param data PuzzleElement representing the cell being modified + */ @Override public void changeCell(MouseEvent e, PuzzleElement data) { BinaryCell cell = (BinaryCell) data; @@ -30,13 +39,13 @@ public void changeCell(MouseEvent e, PuzzleElement data) { } } else { if (e.getButton() == MouseEvent.BUTTON3) { - if (cell.getData() == 0) { + if (cell.getData() == 2) { data.setData(1); } else { if (cell.getData() == 1) { - data.setData(2); - } else { data.setData(0); + } else { + data.setData(2); } } } diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryElementView.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryElementView.java index 9ac99c958..74a761e89 100644 --- a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryElementView.java +++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryElementView.java @@ -7,6 +7,8 @@ public class BinaryElementView extends GridElementView { private static final Font FONT = new Font("TimesRoman", Font.BOLD, 17); private static final Color FONT_COLOR = Color.BLACK; + private static final Color GIVEN_COLOR = Color.LIGHT_GRAY; + private static final Color ELEMENT_COLOR = Color.WHITE; public BinaryElementView(BinaryCell cell) { super(cell); @@ -22,99 +24,64 @@ public BinaryCell getPuzzleElement() { return (BinaryCell) super.getPuzzleElement(); } + /** + * Draws the cells provided in the puzzle's .xml file with light gray background + * + * @param graphics2D The graphics object to draw on + */ @Override public void drawGiven(Graphics2D graphics2D) { - BinaryCell cell = (BinaryCell) puzzleElement; - BinaryType type = cell.getType(); - if (type == BinaryType.ZERO) { - graphics2D.setStroke(new BasicStroke(1)); - graphics2D.setColor(Color.LIGHT_GRAY); - graphics2D.fillRect(location.x, location.y, size.width, size.height); - graphics2D.setColor(Color.BLACK); - graphics2D.drawRect(location.x, location.y, size.width, size.height); - graphics2D.setColor(FONT_COLOR); - graphics2D.setFont(FONT); - FontMetrics metrics = graphics2D.getFontMetrics(FONT); - String value = String.valueOf(puzzleElement.getData()); - int xText = location.x + (size.width - metrics.stringWidth(value)) / 2; - int yText = - location.y + ((size.height - metrics.getHeight()) / 2) + metrics.getAscent(); - graphics2D.drawString(String.valueOf(puzzleElement.getData()), xText, yText); - } else { - if (type == BinaryType.ONE) { - graphics2D.setStroke(new BasicStroke(1)); - graphics2D.setColor(Color.LIGHT_GRAY); - graphics2D.fillRect(location.x, location.y, size.width, size.height); - graphics2D.setColor(Color.BLACK); - graphics2D.drawRect(location.x, location.y, size.width, size.height); - graphics2D.setColor(FONT_COLOR); - graphics2D.setFont(FONT); - FontMetrics metrics = graphics2D.getFontMetrics(FONT); - String value = String.valueOf(puzzleElement.getData()); - int xText = location.x + (size.width - metrics.stringWidth(value)) / 2; - int yText = - location.y - + ((size.height - metrics.getHeight()) / 2) - + metrics.getAscent(); - graphics2D.drawString(String.valueOf(puzzleElement.getData()), xText, yText); - - } else { - if (type == BinaryType.UNKNOWN) { - graphics2D.setStroke(new BasicStroke(0)); - graphics2D.setColor(Color.WHITE); - graphics2D.fillRect(location.x, location.y, size.width, size.height); - graphics2D.setColor(Color.BLACK); - graphics2D.drawRect(location.x, location.y, size.width, size.height); - } - } - } + drawCell(graphics2D, GIVEN_COLOR); } + /** + * Draws new cells being added to board with white background + * + * @param graphics2D The graphics object to draw on + */ @Override public void drawElement(Graphics2D graphics2D) { + drawCell(graphics2D, ELEMENT_COLOR); + } + + /** + * Helper method to handle drawing the cell based on its type and background color + * + * @param graphics2D The graphics object to draw on + * @param bgColor The background color for the cell + */ + private void drawCell(Graphics2D graphics2D, Color bgColor) { BinaryCell cell = (BinaryCell) puzzleElement; BinaryType type = cell.getType(); - if (type == BinaryType.ZERO) { + + if (type == BinaryType.ZERO || type == BinaryType.ONE) { graphics2D.setStroke(new BasicStroke(1)); + graphics2D.setColor(bgColor); + graphics2D.fillRect(location.x, location.y, size.width, size.height); + graphics2D.setColor(Color.BLACK); + graphics2D.drawRect(location.x, location.y, size.width, size.height); + drawCenteredText(graphics2D); + } else if (type == BinaryType.UNKNOWN) { + graphics2D.setStroke(new BasicStroke(0)); graphics2D.setColor(Color.WHITE); graphics2D.fillRect(location.x, location.y, size.width, size.height); graphics2D.setColor(Color.BLACK); graphics2D.drawRect(location.x, location.y, size.width, size.height); - graphics2D.setColor(FONT_COLOR); - graphics2D.setFont(FONT); - FontMetrics metrics = graphics2D.getFontMetrics(FONT); - String value = String.valueOf(puzzleElement.getData()); - int xText = location.x + (size.width - metrics.stringWidth(value)) / 2; - int yText = - location.y + ((size.height - metrics.getHeight()) / 2) + metrics.getAscent(); - graphics2D.drawString(String.valueOf(puzzleElement.getData()), xText, yText); - } else { - if (type == BinaryType.ONE) { - graphics2D.setStroke(new BasicStroke(1)); - graphics2D.setColor(Color.WHITE); - graphics2D.fillRect(location.x, location.y, size.width, size.height); - graphics2D.setColor(Color.BLACK); - graphics2D.drawRect(location.x, location.y, size.width, size.height); - graphics2D.setColor(FONT_COLOR); - graphics2D.setFont(FONT); - FontMetrics metrics = graphics2D.getFontMetrics(FONT); - String value = String.valueOf(puzzleElement.getData()); - int xText = location.x + (size.width - metrics.stringWidth(value)) / 2; - int yText = - location.y - + ((size.height - metrics.getHeight()) / 2) - + metrics.getAscent(); - graphics2D.drawString(String.valueOf(puzzleElement.getData()), xText, yText); - - } else { - if (type == BinaryType.UNKNOWN) { - graphics2D.setStroke(new BasicStroke(0)); - graphics2D.setColor(Color.WHITE); - graphics2D.fillRect(location.x, location.y, size.width, size.height); - graphics2D.setColor(Color.BLACK); - graphics2D.drawRect(location.x, location.y, size.width, size.height); - } - } } } + + /** + * Helper method to draw the centered text within the cell + * + * @param graphics2D The graphics object to draw on + */ + private void drawCenteredText(Graphics2D graphics2D) { + graphics2D.setColor(FONT_COLOR); + graphics2D.setFont(FONT); + FontMetrics metrics = graphics2D.getFontMetrics(FONT); + String value = String.valueOf(puzzleElement.getData()); + int xText = location.x + (size.width - metrics.stringWidth(value)) / 2; + int yText = location.y + ((size.height - metrics.getHeight()) / 2) + metrics.getAscent(); + graphics2D.drawString(value, xText, yText); + } } diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryExporter.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryExporter.java index cd58314b6..c032b63a9 100644 --- a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryExporter.java +++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryExporter.java @@ -10,6 +10,13 @@ public BinaryExporter(Binary binary) { super(binary); } + /** + * Generates an XML element for the binary puzzle board, including its dimensions and the state + * of each cell. Binary cells that are not in the `UNKNOWN` state are included in the XML. + * + * @param newDocument The XML document to which the board element belongs. + * @return The XML element representing the board. + */ @Override protected org.w3c.dom.Element createBoardElement(Document newDocument) { BinaryBoard board; @@ -26,7 +33,7 @@ protected org.w3c.dom.Element createBoardElement(Document newDocument) { org.w3c.dom.Element cellsElement = newDocument.createElement("cells"); for (PuzzleElement puzzleElement : board.getPuzzleElements()) { BinaryCell cell = (BinaryCell) puzzleElement; - if (cell.getData() != -2) { + if (cell.getData() != BinaryType.UNKNOWN.toValue()) { org.w3c.dom.Element cellElement = puzzle.getFactory().exportCell(newDocument, puzzleElement); cellsElement.appendChild(cellElement); diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryImporter.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryImporter.java index 2fc5b09ef..419789060 100644 --- a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryImporter.java +++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryImporter.java @@ -12,16 +12,33 @@ public BinaryImporter(Binary binary) { super(binary); } + /** + * Determines if puzzle uses row and column input + * + * @return true if row and column input is used, false otherwise + */ @Override public boolean acceptsRowsAndColumnsInput() { return true; } + /** + * Determines if puzzle uses text input + * + * @return true if text input is used, false otherwise + */ @Override public boolean acceptsTextInput() { return false; } + /** + * Creates an empty board for building + * + * @param rows the number of rows on the board + * @param columns the number of columns on the board + * @throws RuntimeException if board can not be created + */ @Override public void initializeBoard(int rows, int columns) { BinaryBoard binaryBoard = new BinaryBoard(columns, rows); @@ -48,12 +65,12 @@ public void initializeBoard(Node node) throws InvalidFileFormatException { try { if (!node.getNodeName().equalsIgnoreCase("board")) { throw new InvalidFileFormatException( - "binary Importer: cannot find board puzzleElement"); + "Binary Importer: cannot find board puzzleElement"); } Element boardElement = (Element) node; if (boardElement.getElementsByTagName("cells").getLength() == 0) { throw new InvalidFileFormatException( - "binary Importer: no puzzleElement found for board"); + "Binary Importer: no puzzleElement found for board"); } Element dataElement = (Element) boardElement.getElementsByTagName("cells").item(0); @@ -76,7 +93,7 @@ public void initializeBoard(Node node) throws InvalidFileFormatException { int height = binaryBoard.getHeight(); if (binaryBoard == null || width % 2 != 0 || height % 2 != 0) { - throw new InvalidFileFormatException("binary Importer: invalid board dimensions"); + throw new InvalidFileFormatException("Binary Importer: invalid board dimensions"); } for (int i = 0; i < elementDataList.getLength(); i++) { @@ -110,6 +127,12 @@ public void initializeBoard(Node node) throws InvalidFileFormatException { } } + /** + * Initializes a board with text + * + * @param statements the text being used + * @throws UnsupportedOperationException Binary does not use text input + */ @Override public void initializeBoard(String[] statements) throws UnsupportedOperationException { throw new UnsupportedOperationException("Binary cannot accept text input"); diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryType.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryType.java index 6e3413d7a..7e8e162e6 100644 --- a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryType.java +++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryType.java @@ -1,10 +1,20 @@ package edu.rpi.legup.puzzle.binary; +/** + * Enum representing the possible states of a binary puzzle cell + * + *

The states include: - ZERO: Represents a cell with a value of 0 - ONE: Represents a cell with + * a value of 1 - UNKNOWN: Represents an empty cell + */ public enum BinaryType { - ZERO, - ONE, - UNKNOWN; + ZERO, // Enum constant 0 + ONE, // Enum constant 1 + UNKNOWN; // Enum constant 2 + /** + * The `toValue` method returns the ordinal value of the enum constant, which can be used to + * convert the enum to an integer representation. + */ public int toValue() { return this.ordinal(); } diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryView.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryView.java index b11554f28..b428ce32c 100644 --- a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryView.java +++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryView.java @@ -7,6 +7,14 @@ public class BinaryView extends GridBoardView { + /** + * Creates and arranges the visual components for each cell in the binary puzzle. Initializes + * the view by setting up the board controller, binary controller, and the grid dimensions. For + * each cell in the BinaryBoard, it creates a corresponding BinaryElementView, sets its index, + * size, and location, and adds it to the list of element views to be displayed. + * + * @param board The BinaryBoard representing the current state of the binary puzzle + */ public BinaryView(BinaryBoard board) { super(new BoardController(), new BinaryController(), board.getDimension()); diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/elements/BlankTile.java b/src/main/java/edu/rpi/legup/puzzle/binary/elements/BlankTile.java deleted file mode 100644 index 8b1378917..000000000 --- a/src/main/java/edu/rpi/legup/puzzle/binary/elements/BlankTile.java +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/elements/NumberTile.java b/src/main/java/edu/rpi/legup/puzzle/binary/elements/NumberTile.java index 8b1378917..dc3d75e00 100644 --- a/src/main/java/edu/rpi/legup/puzzle/binary/elements/NumberTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/binary/elements/NumberTile.java @@ -1 +1,13 @@ +package edu.rpi.legup.puzzle.binary.elements; +import edu.rpi.legup.model.elements.PlaceableElement; + +public class NumberTile extends PlaceableElement { + public NumberTile() { + super( + "BINA-ELEM-0001", + "Number Tile", + "A number tile", + "edu/rpi/legup/images/binary/tiles/NumberTile.png"); + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/elements/UnknownTile.java b/src/main/java/edu/rpi/legup/puzzle/binary/elements/UnknownTile.java new file mode 100644 index 000000000..e32a2d12f --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/binary/elements/UnknownTile.java @@ -0,0 +1,13 @@ +package edu.rpi.legup.puzzle.binary.elements; + +import edu.rpi.legup.model.elements.PlaceableElement; + +public class UnknownTile extends PlaceableElement { + public UnknownTile() { + super( + "BINA-ELEM-0002", + "Unknown Tile", + "A blank tile", + "edu/rpi/legup/images/binary/tiles/UnknownTile.png"); + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/elements/binary_elements_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/binary/elements/binary_elements_reference_sheet.txt new file mode 100644 index 000000000..54db0ee0b --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/binary/elements/binary_elements_reference_sheet.txt @@ -0,0 +1,2 @@ +BINA-ELEM-0001 : NumberTile +BINA-ELEM-0002 : UnknownTile \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/CompleteRowColumnDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/CompleteRowColumnDirectRule.java index e38c6b78d..6ced37a9c 100644 --- a/src/main/java/edu/rpi/legup/puzzle/binary/rules/CompleteRowColumnDirectRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/CompleteRowColumnDirectRule.java @@ -14,8 +14,9 @@ public class CompleteRowColumnDirectRule extends DirectRule { public CompleteRowColumnDirectRule() { super( "BINA-BASC-0003", - "Complete Row Column", - "If a row/column of length n contains n/2 of a single value, the remaining cells must contain the other value", + "Complete Row/Column", + "If a row/column of length n contains n/2 of a single value, the remaining " + + "cells must contain the other value", "edu/rpi/legup/images/binary/rules/CompleteRowColumnDirectRule.png"); } @@ -31,23 +32,27 @@ public CompleteRowColumnDirectRule() { @Override public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) { BinaryBoard origBoard = (BinaryBoard) transition.getParents().get(0).getBoard(); - ContradictionRule contraRule = new UnbalancedRowOrColumnContradictionRule(); + ContradictionRule contraRule = new UnbalancedRowColumnContradictionRule(); BinaryCell binaryCell = (BinaryCell) puzzleElement; BinaryBoard modified = origBoard.copy(); - BinaryCell c = (BinaryCell) modified.getPuzzleElement(puzzleElement); - // System.out.println("ORIG" + binaryCell.getData()); - // System.out.println("AFTER" + Math.abs(binaryCell.getData() - 1)); - modified.getPuzzleElement(puzzleElement).setData(binaryCell.getData()); - System.out.println(contraRule.checkContradictionAt(modified, puzzleElement)); - - if (contraRule.checkContradictionAt(modified, puzzleElement) != null) { + // Flip the cell and check to see if there will be an unbalanced row/column contradiction, + // if so the rule is applied correctly + modified.getPuzzleElement(puzzleElement).setData(Math.abs(binaryCell.getData() - 1)); + if (contraRule.checkContradictionAt(modified, puzzleElement) == null) { return null; } - return "Grouping of Three Ones or Zeros not found"; + return "Unbalanced row/column found"; } + /** + * Creates a transition {@link Board} that has this rule applied to it using the {@link + * TreeNode}. + * + * @param node tree node used to create default transition board + * @return default board or null if this rule cannot be applied to this tree node + */ @Override public Board getDefaultBoard(TreeNode node) { return null; diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/EliminateTheImpossibleDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/EliminateTheImpossibleDirectRule.java new file mode 100644 index 000000000..df84dd540 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/EliminateTheImpossibleDirectRule.java @@ -0,0 +1,204 @@ +// package edu.rpi.legup.puzzle.binary.rules; +// +// import edu.rpi.legup.model.gameboard.Board; +// import edu.rpi.legup.model.gameboard.PuzzleElement; +// import edu.rpi.legup.model.rules.DirectRule; +// import edu.rpi.legup.model.tree.TreeNode; +// import edu.rpi.legup.model.tree.TreeTransition; +// import edu.rpi.legup.puzzle.binary.BinaryBoard; +// import edu.rpi.legup.puzzle.binary.BinaryCell; +// import edu.rpi.legup.puzzle.binary.BinaryType; +// +// import java.util.LinkedList; +// import java.util.Queue; +// import java.lang.Math.*; +// import java.lang.reflect.Array; +// import java.util.ArrayList; +// +// public class EliminateTheImpossibleDirectRule extends DirectRule { +// private final String INVALID_USE_MESSAGE = "Number at cell is incorrect"; +// +// public EliminateTheImpossibleDirectRule() { +// super( +// "BINA-BASC-0004", +// "Eliminate The Impossible", +// "Out of the remaining empty cells in this row or column, this digit must go here, +// otherwise there will be a future contradiction", +// "edu/rpi/legup/images/binary/rules/EliminateTheImpossibleDirectRule.png"); +// } +// +// // Function to generate all binary strings +// void generatePossibilitites(int spots, ArrayList possibilities, int zeroCount, int +// oneCount) +// // This function generates all the possible combinations of 0s and 1s for a +// // certain size, it does this +// // by basically just counting from 0 to the number - 1, so if you want all the +// // possible combinations for 3 +// // spots, you can just count in binary from 0 to 7 (taking 3 spots, so from 000 +// // to 111). To be practical, +// // the function does not return an array with all the possibilities as an array, +// // but populates the +// // arraylist you pass in (possibilities) +// { +// if (zeroCount + oneCount != spots) { +// System.out.println("INVALID INPUT"); +// return; +// } +// +// if (zeroCount == spots) { +// String zero = ""; +// for (int i = 0; i < spots; i++) { +// zero = zero + "0"; +// } +// possibilities.add(zero); +// +// } +// int count = (int) Math.pow(2, spots) - 1; +// int finalLen = spots; +// Queue q = new LinkedList(); +// q.add("1"); +// +// while (count-- > 0) { +// String s1 = q.peek(); +// q.remove(); +// +// String newS1 = s1; +// int curLen = newS1.length(); +// int runFor = spots - curLen; +// if (curLen < finalLen) { +// for (int i = 0; i < runFor; i++) { +// newS1 = "0" + newS1; +// } +// } +// int curZeros = 0; +// int curOnes = 0; +// +// for (int i = 0; i < spots; i++) { +// if (newS1.charAt(i) == '0') { +// curZeros++; +// } +// if (newS1.charAt(i) == '1') { +// curOnes++; +// } +// } +// +// if (zeroCount == curZeros && oneCount == curOnes) { +// possibilities.add(newS1); +// } +// String s2 = s1; +// q.add(s1 + "0"); +// q.add(s2 + "1"); +// } +// } +// +// @Override +// public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) { +// // This function should first check if there are three open spaces, if so, +// // continue, else figure out +// // how many spots are open, all the possible binary combinations that could be +// // put there, and by +// // analyzing the common factors, logically determine which number has a set +// // spot, meaning that we know +// // that a certain spot must be a zero or a one +// +// BinaryBoard origBoard = (BinaryBoard) transition.getParents().get(0).getBoard(); +// BinaryCell binaryCell = (BinaryCell) puzzleElement; +// +// //Getting the row where the user clicked +// ArrayList row = origBoard.listRowCells(binaryCell.getLocation().y); +// int size = row.size(); +// int rowNumZeros = 0; +// int rowNumOnes = 0; +// +// for (BinaryCell item : row) { +// if (item.getType() == BinaryType.ZERO) { +// rowNumZeros++; +// } else if (item.getType() == BinaryType.ONE) { +// rowNumOnes++; +// } +// } +// +// ArrayList rowResult = new ArrayList(); +// +// // To call generatePossibilitites(), you must call it and pass in the amount of +// // unknown spots left, +// // an ArrayList that will be populated with the possible results (in String +// // form), the amount of zeros left and ones left +// generatePossibilitites((size - rowNumZeros - rowNumOnes), rowResult, size / 2 - +// rowNumZeros, size / 2 - rowNumOnes); +// +// // Create deep copies of each row +// ArrayList> rowCopies = new ArrayList<>(); +// for (int i = 0; i < rowResult.size(); i++) { +// ArrayList newRow = new ArrayList<>(); +// for (BinaryCell cell : row) { +// newRow.add(cell.copy()); +// } +// rowCopies.add(newRow); +// } +// +// System.out.println("Number of possible binary combinations: " + rowCopies.size()); +// +// ArrayList> nonContraRows = new ArrayList<>(); +// int rowIdx = 0; +// for(ArrayList curRow : rowCopies){ +// int charIdx = 0; +// System.out.println(rowResult.get(rowIdx)); +// for(int i = 0; i < curRow.size(); i++) { +// if (curRow.get(i).getData() == 2) { +// if (rowResult.get(rowIdx).charAt(charIdx) == '0') { +// curRow.get(i).setData(0); +// } +// else if (rowResult.get(rowIdx).charAt(charIdx) == '1') { +// curRow.get(i).setData(1); +// } +// charIdx++; +// } +// System.out.print(curRow.get(i).getData() + " "); +// } +// +// boolean threeAdjacent = false; +// int count = 1; +// for(int i = 1; i < curRow.size(); i++) { +// if (curRow.get(i).getData() == curRow.get(i-1).getData()) { +// count++; +// if (count == 3) { +// threeAdjacent = true; +// break; +// } +// } else { +// count = 1; +// } +// } +// +// if (!threeAdjacent) { +// nonContraRows.add(curRow); +// } +// +// rowIdx++; +// System.out.println(); +// } +// +// System.out.println("Number of non contradiction rows: " + nonContraRows.size()); +// int colNum = binaryCell.getLocation().x; +// boolean invalid = false; +// for(int i = 0; i < nonContraRows.size(); i++) { +// if (nonContraRows.get(i).get(colNum).getData() != binaryCell.getData()) { +// invalid = true; +// break; +// } +// } +// +// if (!invalid) { +// return null; +// } +// +// return "This cell can either be a 0 or a 1"; +// +// } +// +// @Override +// public Board getDefaultBoard(TreeNode node) { +// return null; +// } +// } diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/OneTileGapDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/OneTileGapDirectRule.java deleted file mode 100644 index 2e1e96fa5..000000000 --- a/src/main/java/edu/rpi/legup/puzzle/binary/rules/OneTileGapDirectRule.java +++ /dev/null @@ -1,64 +0,0 @@ -package edu.rpi.legup.puzzle.binary.rules; - -import edu.rpi.legup.model.gameboard.Board; -import edu.rpi.legup.model.gameboard.PuzzleElement; -import edu.rpi.legup.model.rules.ContradictionRule; -import edu.rpi.legup.model.rules.DirectRule; -import edu.rpi.legup.model.tree.TreeNode; -import edu.rpi.legup.model.tree.TreeTransition; -import edu.rpi.legup.puzzle.binary.BinaryBoard; -import edu.rpi.legup.puzzle.binary.BinaryCell; - -public class OneTileGapDirectRule extends DirectRule { - private final String INVALID_USE_MESSAGE = "Number at cell is incorrect"; - - public OneTileGapDirectRule() { - super( - "BINA-BASC-0002", - "One Tile Gap", - "If an empty tile is in between the same value, fill the gap with the other value.", - "edu/rpi/legup/images/binary/rules/OneTileGapDirectRule.png"); - } - - boolean checkLeftRight(BinaryCell c, BinaryBoard board) { - int x = c.getLocation().x; - int y = c.getLocation().y; - return board.getCell(x - 1, y).getType() != c.getType() - || board.getCell(x + 1, y).getType() != c.getType(); - } - - boolean checkUpDown(BinaryCell c, BinaryBoard board) { - int x = c.getLocation().x; - int y = c.getLocation().y; - return board.getCell(x, y - 1).getType() != c.getType() - || board.getCell(x, y + 1).getType() != c.getType(); - } - - @Override - public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) { - BinaryBoard origBoard = (BinaryBoard) transition.getParents().get(0).getBoard(); - ContradictionRule contraRule = new ThreeAdjacentContradictionRule(); - BinaryCell binaryCell = (BinaryCell) puzzleElement; - BinaryBoard modified = origBoard.copy(); - - // System.out.println("ORIG" + binaryCell.getData()); - // System.out.println("AFTER" + Math.abs(binaryCell.getData() - 1)); - modified.getPuzzleElement(puzzleElement).setData(Math.abs(binaryCell.getData() - 1)); - - PuzzleElement newP = binaryCell; - - System.out.println(contraRule.checkContradictionAt(modified, newP)); - - if (contraRule.checkContradictionAt(modified, newP) == null) { - return null; - } - modified.getPuzzleElement(puzzleElement).setData(Math.abs(binaryCell.getData() - 1)); - - return "Grouping of Three Ones or Zeros not found"; - } - - @Override - public Board getDefaultBoard(TreeNode node) { - return null; - } -} diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/PreventTrioDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/PreventTrioDirectRule.java new file mode 100644 index 000000000..728ec8e10 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/PreventTrioDirectRule.java @@ -0,0 +1,59 @@ +package edu.rpi.legup.puzzle.binary.rules; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.gameboard.PuzzleElement; +import edu.rpi.legup.model.rules.DirectRule; +import edu.rpi.legup.model.tree.TreeNode; +import edu.rpi.legup.model.tree.TreeTransition; +import edu.rpi.legup.puzzle.binary.BinaryBoard; +import edu.rpi.legup.puzzle.binary.BinaryCell; + +public class PreventTrioDirectRule extends DirectRule { + private final String INVALID_USE_MESSAGE = "Number at cell is incorrect"; + + public PreventTrioDirectRule() { + super( + "BINA-BASC-0001", + "Prevent Trio", + "If a trio contradiction state could appear, use the opposite digit to prevent the trio", + "edu/rpi/legup/images/binary/rules/PreventTrioDirectRule.png"); + } + + /** + * Checks whether the child node logically follows from the parent node at the specific + * puzzleElement index using this rule + * + * @param transition transition to check + * @param puzzleElement equivalent puzzleElement + * @return null if the child node logically follow from the parent node at the specified + * puzzleElement, otherwise error message + */ + @Override + public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) { + BinaryBoard origBoard = (BinaryBoard) transition.getParents().get(0).getBoard(); + TrioContradictionRule contraRule = new TrioContradictionRule(); + BinaryCell binaryCell = (BinaryCell) puzzleElement; + BinaryBoard modified = origBoard.copy(); + + // Flip the cell and check to see if there will be a trio contradiction, if so the rule is + // applied correctly + modified.getPuzzleElement(puzzleElement).setData(Math.abs(binaryCell.getData() - 1)); + if (contraRule.checkContradictionAt(modified, binaryCell) == null) { + return null; + } + + return "Trio Found"; + } + + /** + * Creates a transition {@link Board} that has this rule applied to it using the {@link + * TreeNode}. + * + * @param node tree node used to create default transition board + * @return default board or null if this rule cannot be applied to this tree node + */ + @Override + public Board getDefaultBoard(TreeNode node) { + return null; + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/DuplicateRowsOrColumnsContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/RepeatedRowColumnContradictionRule.java similarity index 60% rename from src/main/java/edu/rpi/legup/puzzle/binary/rules/DuplicateRowsOrColumnsContradictionRule.java rename to src/main/java/edu/rpi/legup/puzzle/binary/rules/RepeatedRowColumnContradictionRule.java index 8b0d88ae4..a7a4ced12 100644 --- a/src/main/java/edu/rpi/legup/puzzle/binary/rules/DuplicateRowsOrColumnsContradictionRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/RepeatedRowColumnContradictionRule.java @@ -8,30 +8,39 @@ import edu.rpi.legup.puzzle.binary.BinaryType; import java.util.ArrayList; -public class DuplicateRowsOrColumnsContradictionRule extends ContradictionRule { +public class RepeatedRowColumnContradictionRule extends ContradictionRule { private final String NO_CONTRADICTION_MESSAGE = "Does not contain a contradiction at this index"; private final String INVALID_USE_MESSAGE = "Row or column must have a value in each cell"; - public DuplicateRowsOrColumnsContradictionRule() { + public RepeatedRowColumnContradictionRule() { super( "BINA-CONT-0003", - "Duplicate Rows Or Columns", - "There must not be two rows or two columns that are duplicates", - "edu/rpi/legup/images/binary/rules/DuplicateRowOrColumnContradictionRule.png"); + "Repeated Row/Column", + "There must not be two of the same row or two of the same column in the puzzle", + "edu/rpi/legup/images/binary/rules/RepeatedRowColumnContradictionRule.png"); } + /** + * Checks whether the transition has a contradiction at the specific puzzleElement index using + * this rule + * + * @param board board to check contradiction + * @param puzzleElement equivalent puzzleElement + * @return null if the transition contains a contradiction at the specified puzzleElement, + * otherwise error message + */ @Override public String checkContradictionAt(Board board, PuzzleElement puzzleElement) { BinaryBoard binaryBoard = (BinaryBoard) board; BinaryCell cell = (BinaryCell) binaryBoard.getPuzzleElement(puzzleElement); + // Compare each row with row of current cell to see if they are equal, if so the rule is + // applied correctly ArrayList row = binaryBoard.getRowTypes(cell.getLocation().y); - int size = row.size(); - for (int i = 0; i < size; i++) { - if (i > cell.getLocation().y) { + if (i != cell.getLocation().y) { ArrayList currRow = binaryBoard.getRowTypes(i); if (currRow.equals(row)) { return null; @@ -39,10 +48,11 @@ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) { } } + // Compare each column with column of current cell to see if they are equal, if so the rule + // is applied correctly ArrayList col = binaryBoard.getColTypes(cell.getLocation().x); - for (int i = 0; i < size; i++) { - if (i > cell.getLocation().x) { + if (i != cell.getLocation().x) { ArrayList currCol = binaryBoard.getColTypes(i); if (currCol.equals(col)) { return null; diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/SaveBlockerDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/SaveBlockerDirectRule.java new file mode 100644 index 000000000..6cc28446d --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/SaveBlockerDirectRule.java @@ -0,0 +1,59 @@ +package edu.rpi.legup.puzzle.binary.rules; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.gameboard.PuzzleElement; +import edu.rpi.legup.model.rules.DirectRule; +import edu.rpi.legup.model.tree.TreeNode; +import edu.rpi.legup.model.tree.TreeTransition; +import edu.rpi.legup.puzzle.binary.BinaryBoard; +import edu.rpi.legup.puzzle.binary.BinaryCell; + +public class SaveBlockerDirectRule extends DirectRule { + + private final String INVALID_USE_MESSAGE = "Number at cell is incorrect"; + + public SaveBlockerDirectRule() { + super( + "BINA-BASC-0003", + "Save Blocker", + "If a future trio could appear in this row/col, save the digit that could block that trio", + "edu/rpi/legup/images/binary/rules/SaveBlockerDirectRule.png"); + } + + /** + * Checks whether the child node logically follows from the parent node at the specific + * puzzleElement index using this rule + * + * @param transition transition to check + * @param puzzleElement equivalent puzzleElement + * @return null if the child node logically follow from the parent node at the specified + * puzzleElement, otherwise error message + */ + public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) { + BinaryBoard origBoard = (BinaryBoard) transition.getParents().get(0).getBoard(); + WastedBlockerContradictionRule contraRule = new WastedBlockerContradictionRule(); + BinaryCell binaryCell = (BinaryCell) puzzleElement; + BinaryBoard modified = origBoard.copy(); + + // Flip the cell and check to see if a blocker digit is wasted, if so the rule is applied + // correctly + modified.getPuzzleElement(puzzleElement).setData(Math.abs(binaryCell.getData() - 1)); + if (contraRule.checkContradictionAt(modified, binaryCell) == null) { + return null; + } + + return "Wasted Digit Found"; + } + + /** + * Creates a transition {@link Board} that has this rule applied to it using the {@link + * TreeNode}. + * + * @param node tree node used to create default transition board + * @return default board or null if this rule cannot be applied to this tree node + */ + @Override + public Board getDefaultBoard(TreeNode node) { + return null; + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/SurroundPairDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/SurroundPairDirectRule.java deleted file mode 100644 index dc2f07c8b..000000000 --- a/src/main/java/edu/rpi/legup/puzzle/binary/rules/SurroundPairDirectRule.java +++ /dev/null @@ -1,48 +0,0 @@ -package edu.rpi.legup.puzzle.binary.rules; - -import edu.rpi.legup.model.gameboard.Board; -import edu.rpi.legup.model.gameboard.PuzzleElement; -import edu.rpi.legup.model.rules.ContradictionRule; -import edu.rpi.legup.model.rules.DirectRule; -import edu.rpi.legup.model.tree.TreeNode; -import edu.rpi.legup.model.tree.TreeTransition; -import edu.rpi.legup.puzzle.binary.BinaryBoard; -import edu.rpi.legup.puzzle.binary.BinaryCell; - -public class SurroundPairDirectRule extends DirectRule { - - public SurroundPairDirectRule() { - super( - "BINA-BASC-0001", - "Surround Pair", - "If two adjacent tiles have the same value, surround the tiles with the other value.", - "edu/rpi/legup/images/binary/rules/SurroundPairDirectRule.png"); - } - - public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) { - BinaryBoard origBoard = (BinaryBoard) transition.getParents().get(0).getBoard(); - ContradictionRule contraRule = new ThreeAdjacentContradictionRule(); - BinaryCell binaryCell = (BinaryCell) puzzleElement; - BinaryBoard modified = origBoard.copy(); - - // System.out.println("ORIG" + binaryCell.getData()); - // System.out.println("AFTER" + Math.abs(binaryCell.getData() - 1)); - modified.getPuzzleElement(puzzleElement).setData(Math.abs(binaryCell.getData() - 1)); - - PuzzleElement newP = binaryCell; - - System.out.println(contraRule.checkContradictionAt(modified, newP)); - - if (contraRule.checkContradictionAt(modified, newP) == null) { - return null; - } - modified.getPuzzleElement(puzzleElement).setData(Math.abs(binaryCell.getData() - 1)); - - return "Grouping of Three Ones or Zeros not found"; - } - - @Override - public Board getDefaultBoard(TreeNode node) { - return null; - } -} diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/ThreeAdjacentContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/ThreeAdjacentContradictionRule.java deleted file mode 100644 index 075642246..000000000 --- a/src/main/java/edu/rpi/legup/puzzle/binary/rules/ThreeAdjacentContradictionRule.java +++ /dev/null @@ -1,127 +0,0 @@ -package edu.rpi.legup.puzzle.binary.rules; - -import edu.rpi.legup.model.gameboard.Board; -import edu.rpi.legup.model.gameboard.PuzzleElement; -import edu.rpi.legup.model.rules.ContradictionRule; -import edu.rpi.legup.puzzle.binary.BinaryBoard; -import edu.rpi.legup.puzzle.binary.BinaryCell; -import edu.rpi.legup.puzzle.binary.BinaryType; - -public class ThreeAdjacentContradictionRule extends ContradictionRule { - private final String NO_CONTRADICTION_MESSAGE = - "Does not contain a contradiction at this index"; - private final String INVALID_USE_MESSAGE = "Contradiction must be a zero or one"; - - public ThreeAdjacentContradictionRule() { - super( - "BINA-CONT-0001", - "Three Adjacent", - "There must not be three adjacent zeros or three adjacent ones in a row or column", - "edu/rpi/legup/images/binary/rules/ThreeAdjacentContradictionRule.png"); - } - - @Override - public String checkContradictionAt(Board board, PuzzleElement puzzleElement) { - BinaryBoard binaryBoard = (BinaryBoard) board; - int height = binaryBoard.getHeight(); - int width = binaryBoard.getWidth(); - - BinaryCell cell = (BinaryCell) binaryBoard.getPuzzleElement(puzzleElement); - System.out.println("THE CELL IS : " + cell.getType()); - int cellX = cell.getLocation().x; - int cellY = cell.getLocation().y; - BinaryCell oneUp = null; - BinaryCell oneDown = null; - BinaryCell oneForward = null; - BinaryCell oneBackward = null; - BinaryCell twoUp = null; - BinaryCell twoDown = null; - BinaryCell twoForward = null; - BinaryCell twoBackward = null; - if (binaryBoard.getCell(cellX, cellY + 1) != null) { - oneUp = binaryBoard.getCell(cellX, cellY + 1); - } - if (binaryBoard.getCell(cellX, cellY - 1) != null) { - oneDown = binaryBoard.getCell(cellX, cellY - 1); - } - if (binaryBoard.getCell(cellX + 1, cellY) != null) { - oneForward = binaryBoard.getCell(cellX + 1, cellY); - } - if (binaryBoard.getCell(cellX - 1, cellY) != null) { - oneBackward = binaryBoard.getCell(cellX - 1, cellY); - } - if (binaryBoard.getCell(cellX, cellY + 2) != null) { - twoUp = binaryBoard.getCell(cellX, cellY + 2); - } - if (binaryBoard.getCell(cellX, cellY - 2) != null) { - twoDown = binaryBoard.getCell(cellX, cellY - 2); - } - if (binaryBoard.getCell(cellX + 2, cellY) != null) { - twoForward = binaryBoard.getCell(cellX + 2, cellY); - } - if (binaryBoard.getCell(cellX - 2, cellY) != null) { - twoBackward = binaryBoard.getCell(cellX - 2, cellY); - } - - if (cell.getType() == BinaryType.ONE || cell.getType() == BinaryType.ZERO) { - if (twoBackward != null - && oneBackward != null - && twoBackward.getType() != BinaryType.UNKNOWN - && oneBackward.getType() != BinaryType.UNKNOWN) { - if (twoBackward.getType() == cell.getType() - && oneBackward.getType() == cell.getType()) { - System.out.println("1"); - return null; - } - } - if (twoForward != null - && oneForward != null - && twoForward.getType() != BinaryType.UNKNOWN - && oneForward.getType() != BinaryType.UNKNOWN) { - if (twoForward.getType() == cell.getType() - && oneForward.getType() == cell.getType()) { - System.out.println("2"); - return null; - } - } - if (twoDown != null - && oneDown != null - && twoDown.getType() != BinaryType.UNKNOWN - && oneDown.getType() != BinaryType.UNKNOWN) { - if (twoDown.getType() == cell.getType() && oneDown.getType() == cell.getType()) { - System.out.println("3"); - return null; - } - } - if (twoUp != null - && oneUp != null - && twoUp.getType() != BinaryType.UNKNOWN - && oneUp.getType() != BinaryType.UNKNOWN) { - if (twoUp.getType() == cell.getType() && oneUp.getType() == cell.getType()) { - System.out.println("4"); - return null; - } - } - if (oneBackward != null - && oneForward != null - && oneBackward.getType() != BinaryType.UNKNOWN - && oneForward.getType() != BinaryType.UNKNOWN) { - if (oneBackward.getType() == cell.getType() - && oneForward.getType() == cell.getType()) { - System.out.println("5"); - return null; - } - } - if (oneUp != null - && oneDown != null - && oneUp.getType() != BinaryType.UNKNOWN - && oneDown.getType() != BinaryType.UNKNOWN) { - if (oneUp.getType() == cell.getType() && oneDown.getType() == cell.getType()) { - System.out.println("6"); - return null; - } - } - } - return super.getNoContradictionMessage() + ": " + this.NO_CONTRADICTION_MESSAGE; - } -} diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/TrioContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/TrioContradictionRule.java new file mode 100644 index 000000000..fc5cd4165 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/TrioContradictionRule.java @@ -0,0 +1,185 @@ +package edu.rpi.legup.puzzle.binary.rules; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.gameboard.PuzzleElement; +import edu.rpi.legup.model.rules.ContradictionRule; +import edu.rpi.legup.puzzle.binary.BinaryBoard; +import edu.rpi.legup.puzzle.binary.BinaryCell; +import edu.rpi.legup.puzzle.binary.BinaryType; + +public class TrioContradictionRule extends ContradictionRule { + private final String NO_CONTRADICTION_MESSAGE = + "Does not contain a contradiction at this index"; + private final String INVALID_USE_MESSAGE = "Contradiction must be a zero or one"; + + public TrioContradictionRule() { + super( + "BINA-CONT-0001", + "Trio", + "There must not be three adjacent zeros or three adjacent ones in a row or column", + "edu/rpi/legup/images/binary/rules/TrioContradictionRule.png"); + } + + /** + * This method checks the surrounding cells of a given puzzle element at a specified distance in + * both the vertical and horizontal directions + * + * @param board The board where the puzzle elements are located + * @param puzzleElement The puzzle element from which the distance is calculated + * @param n The distance away from the puzzle element to retrieve the surrounding cells + * @return An array of BinaryCells representing the surrounding cells + */ + public BinaryCell[] getCellsNAway(Board board, PuzzleElement puzzleElement, int n) { + BinaryBoard binaryBoard = (BinaryBoard) board; + BinaryCell cell = (BinaryCell) binaryBoard.getPuzzleElement(puzzleElement); + int cellX = cell.getLocation().x; + int cellY = cell.getLocation().y; + + BinaryCell[] cells = new BinaryCell[4]; // [0] up x, [1] down x, [2] right x, [3] left x + cells[0] = null; + cells[1] = null; + cells[2] = null; + cells[3] = null; + + if (binaryBoard.getCell(cellX, cellY + n) != null) { + cells[0] = binaryBoard.getCell(cellX, cellY + n); + } + if (binaryBoard.getCell(cellX, cellY - n) != null) { + cells[1] = binaryBoard.getCell(cellX, cellY - n); + } + if (binaryBoard.getCell(cellX + n, cellY) != null) { + cells[2] = binaryBoard.getCell(cellX + n, cellY); + } + if (binaryBoard.getCell(cellX - n, cellY) != null) { + cells[3] = binaryBoard.getCell(cellX - n, cellY); + } + + return cells; + } + + /** + * Checks whether the cell and its two surrounding cells form a trio of zeros or ones; If a trio + * is found, it indicates a contradiction + * + * @param board The board where the puzzle elements are located + * @param puzzleElement The puzzle element to check for contradiction + * @return true if no contradiction is found, false if contradiction detected + */ + public boolean checkSurroundPair(Board board, PuzzleElement puzzleElement) { + BinaryBoard binaryBoard = (BinaryBoard) board; + BinaryCell cell = (BinaryCell) binaryBoard.getPuzzleElement(puzzleElement); + + // [0] up n, [1] down n, [2] right n, [3] left n + BinaryCell[] cellsOneAway = getCellsNAway(board, puzzleElement, 1); + BinaryCell[] cellsTwoAway = getCellsNAway(board, puzzleElement, 2); + + if (cell.getType() == BinaryType.ONE || cell.getType() == BinaryType.ZERO) { + // left one and left two + if (cellsOneAway[3] != null + && cellsTwoAway[3] != null + && cellsOneAway[3].getType() != BinaryType.UNKNOWN + && cellsTwoAway[3].getType() != BinaryType.UNKNOWN) { + if (cellsOneAway[3].getType() == cell.getType() + && cellsTwoAway[3].getType() == cell.getType()) { + return false; + } + } + // right one and right two + if (cellsOneAway[2] != null + && cellsTwoAway[2] != null + && cellsOneAway[2].getType() != BinaryType.UNKNOWN + && cellsTwoAway[2].getType() != BinaryType.UNKNOWN) { + if (cellsOneAway[2].getType() == cell.getType() + && cellsTwoAway[2].getType() == cell.getType()) { + return false; + } + } + // down one and down two + if (cellsOneAway[1] != null + && cellsTwoAway[1] != null + && cellsOneAway[1].getType() != BinaryType.UNKNOWN + && cellsTwoAway[1].getType() != BinaryType.UNKNOWN) { + if (cellsOneAway[1].getType() == cell.getType() + && cellsTwoAway[1].getType() == cell.getType()) { + return false; + } + } + // up one and up two + if (cellsOneAway[0] != null + && cellsTwoAway[0] != null + && cellsOneAway[0].getType() != BinaryType.UNKNOWN + && cellsTwoAway[0].getType() != BinaryType.UNKNOWN) { + if (cellsOneAway[0].getType() == cell.getType() + && cellsTwoAway[0].getType() == cell.getType()) { + return false; + } + } + } + + return true; + } + + /** + * Checks whether there are two of the same cell type separated by one cell that also has the + * same type in any direction. If a trio is found, it indicates a contradiction + * + * @param board The board where the puzzle elements are located + * @param puzzleElement The puzzle element to check for contradiction + * @return true if no contradiction is found, false if contradiction detected + */ + public boolean checkOneTileGap(Board board, PuzzleElement puzzleElement) { + BinaryBoard binaryBoard = (BinaryBoard) board; + BinaryCell cell = (BinaryCell) binaryBoard.getPuzzleElement(puzzleElement); + + // [0] up n, [1] down n, [2] right n, [3] left n + BinaryCell[] cellsOneAway = getCellsNAway(board, puzzleElement, 1); + + if (cell.getType() == BinaryType.ONE || cell.getType() == BinaryType.ZERO) { + // left one and right one + if (cellsOneAway[3] != null + && cellsOneAway[2] != null + && cellsOneAway[3].getType() != BinaryType.UNKNOWN + && cellsOneAway[2].getType() != BinaryType.UNKNOWN) { + if (cellsOneAway[3].getType() == cell.getType() + && cellsOneAway[2].getType() == cell.getType()) { + return false; + } + } + // down one and up one + if (cellsOneAway[1] != null + && cellsOneAway[0] != null + && cellsOneAway[1].getType() != BinaryType.UNKNOWN + && cellsOneAway[0].getType() != BinaryType.UNKNOWN) { + if (cellsOneAway[1].getType() == cell.getType() + && cellsOneAway[0].getType() == cell.getType()) { + return false; + } + } + } + return true; + } + + /** + * Checks whether the transition has a contradiction at the specific puzzleElement index using + * this rule + * + * @param board board to check contradiction + * @param puzzleElement equivalent puzzleElement + * @return null if the transition contains a contradiction at the specified puzzleElement, + * otherwise error message + */ + @Override + public String checkContradictionAt(Board board, PuzzleElement puzzleElement) { + + boolean surroundPairValid = checkSurroundPair(board, puzzleElement); + if (!surroundPairValid) { + return null; + } + boolean oneTileGapValid = checkOneTileGap(board, puzzleElement); + if (!oneTileGapValid) { + return null; + } + + return super.getNoContradictionMessage() + ": " + this.NO_CONTRADICTION_MESSAGE; + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/UnbalancedRowOrColumnContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/UnbalancedRowColumnContradictionRule.java similarity index 64% rename from src/main/java/edu/rpi/legup/puzzle/binary/rules/UnbalancedRowOrColumnContradictionRule.java rename to src/main/java/edu/rpi/legup/puzzle/binary/rules/UnbalancedRowColumnContradictionRule.java index 5089b3b5f..1d00f4da9 100644 --- a/src/main/java/edu/rpi/legup/puzzle/binary/rules/UnbalancedRowOrColumnContradictionRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/UnbalancedRowColumnContradictionRule.java @@ -8,20 +8,29 @@ import edu.rpi.legup.puzzle.binary.BinaryType; import java.util.Set; -public class UnbalancedRowOrColumnContradictionRule extends ContradictionRule { +public class UnbalancedRowColumnContradictionRule extends ContradictionRule { private final String NO_CONTRADICTION_MESSAGE = "Does not contain a contradiction at this index"; private final String INVALID_USE_MESSAGE = "Row or column must have a value in each cell"; - public UnbalancedRowOrColumnContradictionRule() { + public UnbalancedRowColumnContradictionRule() { super( "BINA-CONT-0002", - "Unbalanced Row Or Column", + "Unbalanced Row/Column", "Each row or column must contain an equal number of zeros and ones", "edu/rpi/legup/images/binary/rules/UnbalancedRowColumnContradictionRule.png"); } + /** + * Checks whether the transition has a contradiction at the specific puzzleElement index using + * this rule + * + * @param board board to check contradiction + * @param puzzleElement equivalent puzzleElement + * @return null if the transition contains a contradiction at the specified puzzleElement, + * otherwise error message + */ @Override public String checkContradictionAt(Board board, PuzzleElement puzzleElement) { BinaryBoard binaryBoard = (BinaryBoard) board; @@ -41,11 +50,12 @@ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) { } } - if (rowNumZeros == size / 2 && rowNumOnes == size / 2) { - return super.getNoContradictionMessage() + ": " + this.NO_CONTRADICTION_MESSAGE; + // if there are too many zeros or ones in this row + if (rowNumZeros > size / 2 || rowNumOnes > size / 2) { + return null; } - Set col = binaryBoard.getCol(cell.getLocation().x); + Set col = binaryBoard.getColCells(cell.getLocation().x); size = col.size(); int colNumZeros = 0; @@ -59,10 +69,11 @@ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) { } } - if (colNumZeros == size / 2 && colNumOnes == size / 2) { - return super.getNoContradictionMessage() + ": " + this.NO_CONTRADICTION_MESSAGE; + // if there are too many zeros or ones in this column + if (colNumZeros > size / 2 || colNumOnes > size / 2) { + return null; } - return null; + return super.getNoContradictionMessage() + ": " + this.NO_CONTRADICTION_MESSAGE; } } diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/UniqueRowColumnDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/UniqueRowColumnDirectRule.java new file mode 100644 index 000000000..0250a4c74 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/UniqueRowColumnDirectRule.java @@ -0,0 +1,299 @@ +package edu.rpi.legup.puzzle.binary.rules; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.gameboard.PuzzleElement; +import edu.rpi.legup.model.rules.DirectRule; +import edu.rpi.legup.model.tree.TreeNode; +import edu.rpi.legup.model.tree.TreeTransition; +import edu.rpi.legup.puzzle.binary.BinaryBoard; +import edu.rpi.legup.puzzle.binary.BinaryCell; +import edu.rpi.legup.puzzle.binary.BinaryType; +import java.util.ArrayList; + +public class UniqueRowColumnDirectRule extends DirectRule { + private final String INVALID_USE_MESSAGE = "Number at cell is incorrect"; + + public UniqueRowColumnDirectRule() { + super( + "BINA-BASC-0004", + "Unique Row/Column", + "If an unfinished row/column only differs by empty cells from a finished one, " + + "fill contradicting empty cells with opposite digit to prevent a repeated row/column", + "edu/rpi/legup/images/binary/rules/UniqueRowColumnDirectRule.png"); + } + + /** + * Counts the number of empty (UNKNOWN) cells in a given sequence + * + * @param seq The sequence of BinaryType elements to check + * @return The number of empty (UNKNOWN) cells in the sequence + */ + private int getNumEmpty(ArrayList seq) { + int numEmpty = 0; + for (BinaryType t : seq) { + if (t.equals(BinaryType.UNKNOWN)) { + numEmpty++; + } + } + return numEmpty; + } + + /** + * Checks if there is a valid opposite digit to prevent a repeated row/column + * + * @param seq The sequence (row or column) to check + * @param origBoard The original board + * @param binaryCell The binary cell being checked + * @param rowOrColumn Flag to indicate whether checking a row (0) or a column (1) + * @return Null if a valid opposite digit is found, otherwise an error message + */ + private String checkOppositeDigitDifference( + ArrayList seq, + BinaryBoard origBoard, + BinaryCell binaryCell, + int rowOrColumn) { + // rowOrColumn : 0 for row, 1 for column + + int numEmpty = getNumEmpty(seq); + if (numEmpty > 2) { + return "Row/Column must have at most 2 empty cells"; + } + + boolean valid = false; + for (int i = 0; i < seq.size(); i++) { + ArrayList currSeq; + // Get the sequence (row or column) from the original board to compare + if (rowOrColumn == 0) { + if (i == binaryCell.getLocation().y) { + continue; + } + currSeq = origBoard.getRowTypes(i); + } else { + if (i == binaryCell.getLocation().x) { + continue; + } + currSeq = origBoard.getColTypes(i); + } + + int numDifferentCells = 0; + for (int j = 0; j < currSeq.size(); j++) { + int numEmptyInCurrSeq = getNumEmpty(currSeq); + // If the current sequence has empty cells, it's not valid for comparison + if (numEmptyInCurrSeq != 0) { + valid = false; + break; + } + // Count differences between the sequences, stopping if more than 1 difference is + // found + if (!seq.get(j).equals(currSeq.get(j)) && !seq.get(j).equals(BinaryType.UNKNOWN)) { + if (++numDifferentCells > 1 || numEmpty != 1) { + valid = false; + break; + } + } + + // Check if there's a contradiction with the current cell, if not mark as valid + if (currSeq.get(j).equals(BinaryType.ZERO) + && seq.get(j).equals(BinaryType.UNKNOWN) + && binaryCell.getType().equals(BinaryType.ONE)) { + if ((rowOrColumn == 0 && binaryCell.getLocation().x == j) + || rowOrColumn == 1 && binaryCell.getLocation().y == j) { + valid = true; + } + } else if (currSeq.get(j).equals(BinaryType.ONE) + && seq.get(j).equals(BinaryType.UNKNOWN) + && binaryCell.getType().equals(BinaryType.ZERO)) { + if ((rowOrColumn == 0 && binaryCell.getLocation().x == j) + || rowOrColumn == 1 && binaryCell.getLocation().y == j) { + valid = true; + } + } + } + // Exit if a valid sequence is found + if (valid) { + break; + } + } + + if (valid) { + return null; + } + return "There does not exist an opposite digit difference in "; + } + + /** + * Checks if there is one digit remaining in a sequence that can be filled to avoid repeating + * another sequence on the board + * + * @param seq The sequence (row or column) to check + * @param origBoard The original board + * @param binaryCell The binary cell being checked + * @param rowOrColumn Flag to indicate whether checking a row (0) or a column (1) + * @param zeroOrOne Flag to indicate whether checking for 0s (0) or 1s (1) + * @return Null if the rule can be applied, otherwise an error message + */ + private String checkRemainingOneDigitDifference( + ArrayList seq, + BinaryBoard origBoard, + BinaryCell binaryCell, + int rowOrColumn, + int zeroOrOne) { + // zeroOrOne: zero for 0, one for 1 + + for (int i = 0; i < seq.size(); i++) { + ArrayList currSeq; + if (rowOrColumn == 0) { + if (i == binaryCell.getLocation().y) { + continue; + } + currSeq = origBoard.getRowTypes(i); + } else { + if (i == binaryCell.getLocation().x) { + continue; + } + currSeq = origBoard.getColTypes(i); + } + + boolean valid = true; + for (int j = 0; j < currSeq.size(); j++) { + int numEmptyInCurrSeq = getNumEmpty(currSeq); + // If the current sequence has empty cells, it's not valid for comparison + if (numEmptyInCurrSeq != 0) { + valid = false; + break; + } + // Check if there is a cell difference from this seq and current seq + if (!seq.get(j).equals(currSeq.get(j)) && !seq.get(j).equals(BinaryType.UNKNOWN)) { + valid = false; + break; + } + } + // Determine if the current sequence can be modified to prevent repetition + if (valid) { + BinaryType currSeqCell = currSeq.get(binaryCell.getLocation().x); + if (rowOrColumn == 0) { + currSeqCell = currSeq.get(binaryCell.getLocation().x); + } else if (rowOrColumn == 1) { + currSeqCell = currSeq.get(binaryCell.getLocation().y); + } + + // Check if this sequence has only one more zero remaining and current sequence + // fills that zero in, + // if so, zero in this seq must go in another cell to prevent repetition + if (zeroOrOne == 0) { + if (currSeqCell.equals(BinaryType.ZERO) + && binaryCell.getType().equals(BinaryType.ONE)) { + return null; + } + } + // Check if this sequence has only one more one remaining and current sequence fills + // that one in, + // if so, one in this seq must go in another cell to prevent repetition + else if (zeroOrOne == 1) { + if (currSeqCell.equals(BinaryType.ONE) + && binaryCell.getType().equals(BinaryType.ZERO)) { + return null; + } + } + } + } + + return "There does not exist a sequence that can be prevented by a remaining digit difference"; + } + + /** + * Checks whether the child node logically follows from the parent node at the specific + * puzzleElement index using this rule + * + * @param transition transition to check + * @param puzzleElement equivalent puzzleElement + * @return null if the child node logically follow from the parent node at the specified + * puzzleElement, otherwise error message + */ + public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) { + BinaryBoard origBoard = (BinaryBoard) transition.getParents().get(0).getBoard(); + BinaryCell binaryCell = (BinaryCell) puzzleElement; + + // Check if filling the current cell with the opposite digit would prevent repetition with + // another row + ArrayList row = origBoard.getRowTypes(binaryCell.getLocation().y); + if (checkOppositeDigitDifference(row, origBoard, binaryCell, 0) == null) { + return null; + } + int numZeros = 0; + int numOnes = 0; + for (int i = 0; i < row.size(); i++) { + if (row.get(i).equals(BinaryType.ZERO)) { + numZeros++; + } else if (row.get(i).equals(BinaryType.ONE)) { + numOnes++; + } + } + + // Check if only one more zero is needed, then see this row will be repeated by another row + // if current cell is filled in with last zero as well + if (numZeros == row.size() / 2 - 1) { + if (checkRemainingOneDigitDifference(row, origBoard, binaryCell, 0, 0) == null) { + return null; + } + } + + // Check if only one more one is needed, then see this row will be repeated by another row + // if current cell is filled in with last one as well + if (numOnes == row.size() / 2 - 1) { + if (checkRemainingOneDigitDifference(row, origBoard, binaryCell, 0, 1) == null) { + return null; + } + } + + // Check if filling the current cell with the opposite digit would prevent repetition with + // another column + ArrayList col = origBoard.getColTypes(binaryCell.getLocation().x); + if (checkOppositeDigitDifference(col, origBoard, binaryCell, 1) == null) { + return null; + } + + numZeros = 0; + numOnes = 0; + for (int i = 0; i < col.size(); i++) { + if (col.get(i).equals(BinaryType.ZERO)) { + numZeros++; + } else if (col.get(i).equals(BinaryType.ONE)) { + numOnes++; + } + } + + // Check if only one more zero is needed, then see this column will be repeated by another + // column + // if current cell is filled in with last zero as well + if (numZeros == col.size() / 2 - 1) { + if (checkRemainingOneDigitDifference(col, origBoard, binaryCell, 1, 0) == null) { + return null; + } + } + + // Check if only one more one is needed, then see this column will be repeated by another + // column + // if current cell is filled in with last one as well + if (numOnes == col.size() / 2 - 1) { + if (checkRemainingOneDigitDifference(col, origBoard, binaryCell, 1, 1) == null) { + return null; + } + } + + return "There is no row/column that forces this cell to be a " + + binaryCell.getData().toString(); + } + + /** + * Creates a transition {@link Board} that has this rule applied to it using the {@link + * TreeNode}. + * + * @param node tree node used to create default transition board + * @return default board or null if this rule cannot be applied to this tree node + */ + @Override + public Board getDefaultBoard(TreeNode node) { + return null; + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/WastedBlockerContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/WastedBlockerContradictionRule.java new file mode 100644 index 000000000..ef5fc0e7e --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/WastedBlockerContradictionRule.java @@ -0,0 +1,180 @@ +package edu.rpi.legup.puzzle.binary.rules; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.gameboard.PuzzleElement; +import edu.rpi.legup.model.rules.ContradictionRule; +import edu.rpi.legup.puzzle.binary.BinaryBoard; +import edu.rpi.legup.puzzle.binary.BinaryCell; +import edu.rpi.legup.puzzle.binary.BinaryType; +import java.util.ArrayList; + +public class WastedBlockerContradictionRule extends ContradictionRule { + private final String NO_CONTRADICTION_MESSAGE = + "Does not contain a contradiction at this index"; + + public WastedBlockerContradictionRule() { + super( + "BINA-CONT-0004", + "Wasted Blocker", + "There exists a cell in this row/column that allocates a digit unnecessarily and" + + " will cause a future trio to appear", + "edu/rpi/legup/images/binary/rules/WastedBlockerContradictionRule.png"); + } + + /* + i [ n ] j + i -> digit on left (0 if no digit exists) + n -> number of empty cells + j -> digit on right (0 if no digit exists) + neededZeros = ( n + i + j ) / 3 + */ + /** + * Calculates the number of zeros needed in a sequence based on the values on either side and + * the number of empty cells. + * + * @param leftVal The value on the left side of the empty cells + * @param rightVal The value on the right side of the empty cells + * @param emptyCellsInCurSec The number of empty cells in the current section + * @return The number of zeros needed in the sequence + */ + private int calculateNeededZeros(int leftVal, int rightVal, int emptyCellsInCurSec) { + int leftCopy = leftVal; + int rightCopy = rightVal; + if (leftCopy == -1) { + leftCopy = 0; + } + if (rightCopy == -1) { + rightCopy = 0; + } + return ((emptyCellsInCurSec + leftCopy + rightCopy) / 3); + } + + /* + i [ n ] j + i -> digit on left (1 if no digit exists) + n -> number of empty cells + j -> digit on right (1 if no digit exists) + neededOnes = ( n + ( 1 - i ) + ( 1 - j ) ) / 3 + */ + /** + * Calculates the number of ones needed in a sequence based on the values on either side and the + * number of empty cells + * + * @param leftVal The value on the left side of the empty cells + * @param rightVal The value on the right side of the empty cells + * @param emptyCellsInCurSec The number of empty cells in the current section + * @return The number of ones needed in the sequence + */ + private int calculateNeededOnes(int leftVal, int rightVal, int emptyCellsInCurSec) { + int leftCopy = leftVal; + int rightCopy = rightVal; + if (leftCopy == -1) { + leftCopy = 1; + } + if (rightCopy == -1) { + rightCopy = 1; + } + return ((emptyCellsInCurSec + (1 - leftCopy) + (1 - rightCopy)) / 3); + } + + /** + * Checks a sequence (row or column) to see if a wasted blocker digit is used + * + * @param seq The sequence to check + * @return Null if the sequence contains a contradiction, otherwise an error message + */ + private String checkSequence(ArrayList seq) { + int numZeros = 0; + int numOnes = 0; + boolean emptyCell = false; + int emptyCellsInCurSec = 0; + int neededZeros = 0; + int neededOnes = 0; + + for (int i = 0; i < seq.size(); i++) { + if (seq.get(i).equals(BinaryType.ZERO) || seq.get(i).equals(BinaryType.ONE)) { + if (seq.get(i).equals(BinaryType.ZERO)) { + numZeros++; + } else if (seq.get(i).equals(BinaryType.ONE)) { + numOnes++; + } + + if (emptyCell) { + if (emptyCellsInCurSec > 1) { // Ignore case where there is only one empty cell + int leftVal; + int rightVal; + // Check if left cell is out of bounds + if (i - emptyCellsInCurSec - 1 < 0) { + leftVal = -1; + } else { + leftVal = seq.get(i - emptyCellsInCurSec - 1).toValue(); + } + rightVal = seq.get(i).toValue(); + neededZeros += calculateNeededZeros(leftVal, rightVal, emptyCellsInCurSec); + neededOnes += calculateNeededOnes(leftVal, rightVal, emptyCellsInCurSec); + } + emptyCell = false; + emptyCellsInCurSec = 0; + } + } else { + if (!emptyCell) { + emptyCell = true; + } + emptyCellsInCurSec++; + } + } + + // Check last cell is empty + if (emptyCell) { + if (emptyCellsInCurSec > 1) { // Ignore case where there is only one empty cell + int leftVal; + int rightVal; + // Check if left cell is out of bounds + if (seq.size() - 1 - emptyCellsInCurSec - 1 < 0) { + leftVal = -1; + } else { + leftVal = seq.get(seq.size() - 1 - emptyCellsInCurSec).toValue(); + } + rightVal = -1; + neededZeros += calculateNeededZeros(leftVal, rightVal, emptyCellsInCurSec); + neededOnes += calculateNeededOnes(leftVal, rightVal, emptyCellsInCurSec); + } + } + + // Check if the number of needed zeros or ones exceeds half the sequence length + // If so, return null to indicate contradiction has occurred + if ((numZeros + neededZeros > seq.size() / 2) || (numOnes + neededOnes > seq.size() / 2)) { + return null; + } + + return super.getNoContradictionMessage() + ": " + this.NO_CONTRADICTION_MESSAGE; + } + + /** + * Checks whether the transition has a contradiction at the specific puzzleElement index using + * this rule + * + * @param board board to check contradiction + * @param puzzleElement equivalent puzzleElement + * @return null if the transition contains a contradiction at the specified puzzleElement, + * otherwise error message + */ + @Override + public String checkContradictionAt(Board board, PuzzleElement puzzleElement) { + + BinaryBoard binaryBoard = (BinaryBoard) board; + BinaryCell cell = (BinaryCell) binaryBoard.getPuzzleElement(puzzleElement); + + ArrayList row = binaryBoard.getRowTypes(cell.getLocation().y); + if (checkSequence(row) == null) { + return null; + } + + ArrayList col = binaryBoard.getColTypes(cell.getLocation().x); + if (checkSequence(col) == null) { + return null; + } + + return super.getNoContradictionMessage() + ": " + this.NO_CONTRADICTION_MESSAGE; + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/OneOrZeroCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/ZeroOrOneCaseRule.java similarity index 62% rename from src/main/java/edu/rpi/legup/puzzle/binary/rules/OneOrZeroCaseRule.java rename to src/main/java/edu/rpi/legup/puzzle/binary/rules/ZeroOrOneCaseRule.java index 70549cd72..f47fe55b9 100644 --- a/src/main/java/edu/rpi/legup/puzzle/binary/rules/OneOrZeroCaseRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/ZeroOrOneCaseRule.java @@ -11,16 +11,23 @@ import java.util.ArrayList; import java.util.List; -public class OneOrZeroCaseRule extends CaseRule { +public class ZeroOrOneCaseRule extends CaseRule { - public OneOrZeroCaseRule() { + public ZeroOrOneCaseRule() { super( "BINA-CASE-0001", - "One or Zero", - "Each blank cell is either a one or a zero.", - "edu/rpi/legup/images/binary/rules/OneOrZeroCaseRule.png"); + "Zero Or One", + "Each blank cell is either a zero or a one", + "edu/rpi/legup/images/binary/rules/ZeroOrOneCaseRule.png"); } + /** + * Checks whether the {@link TreeTransition} logically follows from the parent node using this + * rule. This method is the one that should be overridden in child classes. + * + * @param transition transition to check + * @return null if the child node logically follow from the parent node, otherwise error message + */ @Override public String checkRuleRaw(TreeTransition transition) { List childTransitions = transition.getParents().get(0).getChildren(); @@ -46,12 +53,19 @@ public String checkRuleRaw(TreeTransition transition) { if (!((mod1.getType() == BinaryType.ZERO && mod2.getType() == BinaryType.ONE) || (mod2.getType() == BinaryType.ZERO && mod1.getType() == BinaryType.ONE))) { return super.getInvalidUseOfRuleMessage() - + ": This case rule must an empty white and black cell."; + + ": This case rule must modify an empty cell."; } return null; } + /** + * Generates a {@link CaseBoard} that includes all blank cells from the given board that this + * case rule can be applied to + * + * @param board The board to find locations where this case rule can be applied + * @return A CaseBoard containing pickable elements where the case rule can be applied + */ @Override public CaseBoard getCaseBoard(Board board) { BinaryBoard binaryBoard = (BinaryBoard) board.copy(); @@ -65,24 +79,44 @@ public CaseBoard getCaseBoard(Board board) { return caseBoard; } + /** + * Gets the possible cases at a specific location based on this case rule + * + * @param board the current board state + * @param puzzleElement equivalent puzzleElement + * @return a list of elements the specified could be + */ @Override public ArrayList getCases(Board board, PuzzleElement puzzleElement) { ArrayList cases = new ArrayList<>(); + if (puzzleElement == null) { + return cases; + } + Board case1 = board.copy(); PuzzleElement data1 = case1.getPuzzleElement(puzzleElement); - data1.setData(BinaryType.ZERO.toValue()); + data1.setData(BinaryType.ONE.toValue()); case1.addModifiedData(data1); cases.add(case1); Board case2 = board.copy(); PuzzleElement data2 = case2.getPuzzleElement(puzzleElement); - data2.setData(BinaryType.ONE.toValue()); + data2.setData(BinaryType.ZERO.toValue()); case2.addModifiedData(data2); cases.add(case2); return cases; } + /** + * Checks whether the child node logically follows from the parent node at the specific + * puzzleElement index using this rule + * + * @param transition transition to check + * @param puzzleElement equivalent puzzleElement + * @return null if the child node logically follow from the parent node at the specified + * puzzleElement, otherwise error message + */ @Override public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) { return null; diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/binary_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/binary/rules/binary_reference_sheet.txt index c8cb0d1b9..619d183a5 100644 --- a/src/main/java/edu/rpi/legup/puzzle/binary/rules/binary_reference_sheet.txt +++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/binary_reference_sheet.txt @@ -1,9 +1,11 @@ -BINA-BASC-0001 : SurroundPairDirectRule -BINA-BASC-0002 : OneTileGapDirectRule -BINA-BASC-0003 : CompleteRowColumnDirectRule +BINA-BASC-0001 : PreventTrioContradictionRule +BINA-BASC-0002 : CompleteRowColumnDirectRule +BINA-BASC-0003 : SaveBlockerDirectRule +BINA-BASC-0004 : UniqueRowColumnDirectRule -BINA-CONT-0001 : ThreeAdjacentContradictionRule -BINA-CONT-0002 : UnbalancedRowOrColumnContradictionRule -BINA-CONT-0003 : DuplicateRowsOrColumnsContradictionRule +BINA-CONT-0001 : TrioContradictionRule +BINA-CONT-0002 : UnbalancedRowColumnContradictionRule +BINA-CONT-0003 : RepeatedRowColumnContradictionRule +BINA-CONT-0004 : WastedBlockerContradictionRule -BINA-CASE-0001 : OneOrZeroCaseRule \ No newline at end of file +BINA-CASE-0001 : ZeroOrOneCaseRule \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixCell.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixCell.java index a9e5aa2df..40c5e4a54 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixCell.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixCell.java @@ -42,13 +42,13 @@ public void setCellType(FillapixCellType type) { @Override public void setType(Element e, MouseEvent m) { switch (e.getElementID()) { - case "FPIX-PLAC-0001": + case "FPIX-ELEM-0001": this.setCellType(FillapixCellType.BLACK); break; - case "FPIX-PLAC-0002": + case "FPIX-ELEM-0004": this.setCellType(FillapixCellType.WHITE); break; - case "FPIX-UNPL-0001": + case "FPIX-ELEM-0002": int n = this.getNumber(); switch (m.getButton()) { case MouseEvent.BUTTON1: diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/BlackTile.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/BlackTile.java index 1d7c038a3..a6993778d 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/BlackTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/BlackTile.java @@ -5,7 +5,7 @@ public class BlackTile extends PlaceableElement { public BlackTile() { super( - "FPIX-PLAC-0001", + "FPIX-ELEM-0001", "Black Tile", "The black tile", "edu/rpi/legup/images/fillapix/tiles/BlackTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/NumberTile.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/NumberTile.java index e869aeaf9..5852c1ad7 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/NumberTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/NumberTile.java @@ -1,13 +1,13 @@ package edu.rpi.legup.puzzle.fillapix.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class NumberTile extends NonPlaceableElement { +public class NumberTile extends PlaceableElement { private int object_num; public NumberTile() { super( - "FPIX-UNPL-0001", + "FPIX-ELEM-0002", "Number Tile", "A numbered tile", "edu/rpi/legup/images/fillapix/tiles/NumberTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/UnknownTile.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/UnknownTile.java index 6778c1758..82d0dffb9 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/UnknownTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/UnknownTile.java @@ -1,11 +1,11 @@ package edu.rpi.legup.puzzle.fillapix.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class UnknownTile extends NonPlaceableElement { +public class UnknownTile extends PlaceableElement { public UnknownTile() { super( - "FPIX-UNPL-0002", + "FPIX-ELEM-0003", "Unknown Tile", "A blank tile", "edu/rpi/legup/images/fillapix/tiles/UnknownTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/WhiteTile.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/WhiteTile.java index 67065a7e9..b2eedfc09 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/WhiteTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/WhiteTile.java @@ -5,7 +5,7 @@ public class WhiteTile extends PlaceableElement { public WhiteTile() { super( - "FPIX-PLAC-0002", + "FPIX-ELEM-0004", "White Tile", "The white tile", "edu/rpi/legup/images/fillapix/tiles/WhiteTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/fillapix_elements_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/fillapix_elements_reference_sheet.txt index 0409fa800..1aece4b97 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/fillapix_elements_reference_sheet.txt +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/fillapix_elements_reference_sheet.txt @@ -1,5 +1,4 @@ -FPIX-PLAC-0001 : BlackTile -FPIX-PLAC-0002 : WhiteTile - -FPIX-UNPL-0001 : NumberTile -FPIX-UNPL-0002 : UnknownTile \ No newline at end of file +FPIX-ELEM-0001 : BlackTile +FPIX-ELEM-0002 : NumberTile +FPIX-ELEM-0003 : UnknownTile +FPIX-ELEM-0004 : WhiteTile \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/BlackOrWhiteCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/BlackOrWhiteCaseRule.java index 860a6c011..f0194bd39 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/BlackOrWhiteCaseRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/BlackOrWhiteCaseRule.java @@ -37,6 +37,9 @@ public CaseBoard getCaseBoard(Board board) { @Override public ArrayList getCases(Board board, PuzzleElement puzzleElement) { ArrayList cases = new ArrayList<>(); + if (puzzleElement == null) { + return cases; + } Board case1 = board.copy(); FillapixCell cell1 = (FillapixCell) case1.getPuzzleElement(puzzleElement); diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/SatisfyClueCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/SatisfyClueCaseRule.java index 7db833f76..f8bb2d4f5 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/SatisfyClueCaseRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/SatisfyClueCaseRule.java @@ -45,6 +45,9 @@ public CaseBoard getCaseBoard(Board board) { @Override public ArrayList getCases(Board board, PuzzleElement puzzleElement) { ArrayList cases = new ArrayList(); + if (puzzleElement == null) { + return cases; + } // get value of cell FillapixBoard fillapixBoard = (FillapixBoard) board.copy(); diff --git a/src/main/java/edu/rpi/legup/puzzle/lightup/LightUp.java b/src/main/java/edu/rpi/legup/puzzle/lightup/LightUp.java index ab95c4658..a73806cd7 100644 --- a/src/main/java/edu/rpi/legup/puzzle/lightup/LightUp.java +++ b/src/main/java/edu/rpi/legup/puzzle/lightup/LightUp.java @@ -47,7 +47,7 @@ public Board generatePuzzle(int difficulty) { * @return true if the given dimensions are valid for Light Up, false otherwise */ public boolean isValidDimensions(int rows, int columns) { - return rows > 0 && columns > 0; + return rows >= 0 && columns >= 0; } /** diff --git a/src/main/java/edu/rpi/legup/puzzle/lightup/LightUpBoard.java b/src/main/java/edu/rpi/legup/puzzle/lightup/LightUpBoard.java index 217ef79a8..21084b8c7 100644 --- a/src/main/java/edu/rpi/legup/puzzle/lightup/LightUpBoard.java +++ b/src/main/java/edu/rpi/legup/puzzle/lightup/LightUpBoard.java @@ -134,12 +134,12 @@ public int getNumAdjLite(LightUpCell cell) { } /** - * Gets the number of adjacent cells that are placable + * Gets the number of adjacent cells that are placeable * * @param cell specified cell - * @return number of adjacent cells that are placable + * @return number of adjacent cells that are placeable */ - public int getNumPlacble(LightUpCell cell) { + public int getNumPlaceable(LightUpCell cell) { int num = 0; Set adjCells = getAdj(cell); for (LightUpCell c : adjCells) { diff --git a/src/main/java/edu/rpi/legup/puzzle/lightup/LightUpCell.java b/src/main/java/edu/rpi/legup/puzzle/lightup/LightUpCell.java index 8adf84cb4..6d890e67b 100644 --- a/src/main/java/edu/rpi/legup/puzzle/lightup/LightUpCell.java +++ b/src/main/java/edu/rpi/legup/puzzle/lightup/LightUpCell.java @@ -16,16 +16,16 @@ public LightUpCell(int valueInt, Point location) { @Override public void setType(Element e, MouseEvent m) { switch (e.getElementID()) { - case "LTUP-PLAC-0001": + case "LTUP-ELEM-0002": this.data = -4; break; - case "LTUP-UNPL-0002": + case "LTUP-ELEM-0001": this.data = -1; break; - case "LTUP-UNPL-0003": + case "LTUP-ELEM-0004": this.data = -2; break; - case "LTUP-UNPL-0001": + case "LTUP-ELEM-0003": switch (m.getButton()) { case MouseEvent.BUTTON1: if (this.data < 0 || this.data > 3) { diff --git a/src/main/java/edu/rpi/legup/puzzle/lightup/elements/BlackTile.java b/src/main/java/edu/rpi/legup/puzzle/lightup/elements/BlackTile.java index 2ddb4f754..eed3795d7 100644 --- a/src/main/java/edu/rpi/legup/puzzle/lightup/elements/BlackTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/lightup/elements/BlackTile.java @@ -1,11 +1,11 @@ package edu.rpi.legup.puzzle.lightup.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class BlackTile extends NonPlaceableElement { +public class BlackTile extends PlaceableElement { public BlackTile() { super( - "LTUP-UNPL-0002", + "LTUP-ELEM-0001", "Black Tile", "The black tile", "edu/rpi/legup/images/lightup/black.gif"); diff --git a/src/main/java/edu/rpi/legup/puzzle/lightup/elements/BulbTile.java b/src/main/java/edu/rpi/legup/puzzle/lightup/elements/BulbTile.java index d238baa56..61ebac3d0 100644 --- a/src/main/java/edu/rpi/legup/puzzle/lightup/elements/BulbTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/lightup/elements/BulbTile.java @@ -5,7 +5,7 @@ public class BulbTile extends PlaceableElement { public BulbTile() { super( - "LTUP-PLAC-0001", + "LTUP-ELEM-0002", "Bulb Tile", "The bulb tile", "edu/rpi/legup/images/lightup/light.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/lightup/elements/NumberTile.java b/src/main/java/edu/rpi/legup/puzzle/lightup/elements/NumberTile.java index ae314a4cf..26f9be46c 100644 --- a/src/main/java/edu/rpi/legup/puzzle/lightup/elements/NumberTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/lightup/elements/NumberTile.java @@ -1,15 +1,15 @@ package edu.rpi.legup.puzzle.lightup.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class NumberTile extends NonPlaceableElement { +public class NumberTile extends PlaceableElement { int object_number; // Follow the default format and resolves the NoSuchMethod error public NumberTile() { super( - "LTUP-UNPL-0001", + "LTUP-ELEM-0003", "Number Tile", "The number tile", "edu/rpi/legup/images/lightup/1.gif"); @@ -17,7 +17,7 @@ public NumberTile() { public NumberTile(int num) { super( - "LTUP-UNPL-0001", + "LTUP-ELEM-0003", "Number Tile", "The number tile", "edu/rpi/legup/images/lightup/" + num + ".gif"); diff --git a/src/main/java/edu/rpi/legup/puzzle/lightup/elements/UnknownTile.java b/src/main/java/edu/rpi/legup/puzzle/lightup/elements/UnknownTile.java index 24d420fe8..a724be600 100644 --- a/src/main/java/edu/rpi/legup/puzzle/lightup/elements/UnknownTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/lightup/elements/UnknownTile.java @@ -1,11 +1,11 @@ package edu.rpi.legup.puzzle.lightup.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class UnknownTile extends NonPlaceableElement { +public class UnknownTile extends PlaceableElement { public UnknownTile() { super( - "LTUP-UNPL-0003", + "LTUP-ELEM-0004", "Unknown Tile", "A blank tile", "edu/rpi/legup/images/lightup/UnknownTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/lightup/elements/lightup_elements_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/lightup/elements/lightup_elements_reference_sheet.txt new file mode 100644 index 000000000..93c97de1c --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/lightup/elements/lightup_elements_reference_sheet.txt @@ -0,0 +1,4 @@ +LTUP-ELEM-0001 : BlackTile +LTUP-ELEM-0002 : BulbTile +LTUP-ELEM-0003 : NumberTile +LTUP-ELEM-0004 : UnknownTile \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/lightup/rules/LightOrEmptyCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/lightup/rules/LightOrEmptyCaseRule.java index 4ba754731..53efb6587 100644 --- a/src/main/java/edu/rpi/legup/puzzle/lightup/rules/LightOrEmptyCaseRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/lightup/rules/LightOrEmptyCaseRule.java @@ -44,6 +44,10 @@ public CaseBoard getCaseBoard(Board board) { @Override public ArrayList getCases(Board board, PuzzleElement puzzleElement) { ArrayList cases = new ArrayList<>(); + if (puzzleElement == null) { + return cases; + } + Board case1 = board.copy(); PuzzleElement data1 = case1.getPuzzleElement(puzzleElement); data1.setData(-4); diff --git a/src/main/java/edu/rpi/legup/puzzle/lightup/rules/SatisfyNumberCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/lightup/rules/SatisfyNumberCaseRule.java index 490122874..f73a34b2d 100644 --- a/src/main/java/edu/rpi/legup/puzzle/lightup/rules/SatisfyNumberCaseRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/lightup/rules/SatisfyNumberCaseRule.java @@ -47,6 +47,11 @@ public CaseBoard getCaseBoard(Board board) { */ @Override public ArrayList getCases(Board board, PuzzleElement puzzleElement) { + ArrayList cases = new ArrayList<>(); + if (puzzleElement == null) { + return cases; + } + LightUpBoard lightUpBoard = (LightUpBoard) board; LightUpCell cell = (LightUpCell) puzzleElement; Point loc = cell.getLocation(); @@ -96,7 +101,6 @@ public ArrayList getCases(Board board, PuzzleElement puzzleElement) { } } - ArrayList cases = new ArrayList<>(); if (numNeeded == 0) { return cases; } diff --git a/src/main/java/edu/rpi/legup/puzzle/lightup/rules/TooFewBulbsContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/lightup/rules/TooFewBulbsContradictionRule.java index 8cf68e570..de1f85edc 100644 --- a/src/main/java/edu/rpi/legup/puzzle/lightup/rules/TooFewBulbsContradictionRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/lightup/rules/TooFewBulbsContradictionRule.java @@ -36,7 +36,7 @@ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) { } int bulbs = lightUpBoard.getNumAdj(cell, LightUpCellType.BULB); - int placeable = lightUpBoard.getNumPlacble(cell); + int placeable = lightUpBoard.getNumPlaceable(cell); if (bulbs + placeable < cell.getData()) { return null; diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/BombTile.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/BombTile.java index 78a5d320c..cd5577eeb 100644 --- a/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/BombTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/BombTile.java @@ -1,8 +1,8 @@ package edu.rpi.legup.puzzle.minesweeper.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class BombTile extends NonPlaceableElement { +public class BombTile extends PlaceableElement { public BombTile() { super( "MINE-UNPL-0001", diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/UnsetTile.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/UnsetTile.java index 447e2840c..6cfc34c8d 100644 --- a/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/UnsetTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/UnsetTile.java @@ -1,8 +1,8 @@ package edu.rpi.legup.puzzle.minesweeper.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class UnsetTile extends NonPlaceableElement { +public class UnsetTile extends PlaceableElement { public UnsetTile() { super( diff --git a/src/main/java/edu/rpi/legup/puzzle/nurikabe/NurikabeCell.java b/src/main/java/edu/rpi/legup/puzzle/nurikabe/NurikabeCell.java index c6cd2c64e..1e0e85ed8 100644 --- a/src/main/java/edu/rpi/legup/puzzle/nurikabe/NurikabeCell.java +++ b/src/main/java/edu/rpi/legup/puzzle/nurikabe/NurikabeCell.java @@ -46,13 +46,13 @@ public NurikabeType getType() { @Override public void setType(Element e, MouseEvent m) { switch (e.getElementID()) { - case "NURI-PLAC-0001": + case "NURI-ELEM-0001": this.data = -1; break; - case "NURI-PLAC-0002": + case "NURI-ELEM-0004": this.data = 0; break; - case "NURI-UNPL-0001": + case "NURI-ELEM-0002": switch (m.getButton()) { case MouseEvent.BUTTON1: if (this.data <= 0 || this.data > 8) { diff --git a/src/main/java/edu/rpi/legup/puzzle/nurikabe/NurikabeController.java b/src/main/java/edu/rpi/legup/puzzle/nurikabe/NurikabeController.java index 158abe7b4..44ead6fac 100644 --- a/src/main/java/edu/rpi/legup/puzzle/nurikabe/NurikabeController.java +++ b/src/main/java/edu/rpi/legup/puzzle/nurikabe/NurikabeController.java @@ -6,6 +6,15 @@ public class NurikabeController extends ElementController { + /** + * Handles cell state changes in the nurikabe puzzle when a mouse event occurs If the left mouse + * button is clicked: - If the control key is held down, shows a context menu at the mouse + * position - Otherwise, toggles the cell data state between 0, -1, and -2 in a cyclic manner If + * the right mouse button is clicked, the cell data state is also toggled between -2, -1, and 0 + * + * @param e MouseEvent triggered by the user interaction + * @param data PuzzleElement representing the cell being modified + */ @Override public void changeCell(MouseEvent e, PuzzleElement data) { NurikabeCell cell = (NurikabeCell) data; diff --git a/src/main/java/edu/rpi/legup/puzzle/nurikabe/NurikabeExporter.java b/src/main/java/edu/rpi/legup/puzzle/nurikabe/NurikabeExporter.java index 23efd4724..e01821639 100644 --- a/src/main/java/edu/rpi/legup/puzzle/nurikabe/NurikabeExporter.java +++ b/src/main/java/edu/rpi/legup/puzzle/nurikabe/NurikabeExporter.java @@ -10,6 +10,13 @@ public NurikabeExporter(Nurikabe nurikabe) { super(nurikabe); } + /** + * Generates an XML element for the nurikabe puzzle board, including its dimensions and the + * state of each cell. Nurikabe cells that are not empty are included in the XML. + * + * @param newDocument The XML document to which the board element belongs. + * @return The XML element representing the board. + */ @Override protected org.w3c.dom.Element createBoardElement(Document newDocument) { NurikabeBoard board; diff --git a/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/BlackTile.java b/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/BlackTile.java index 459a809e0..a7972b9b2 100644 --- a/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/BlackTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/BlackTile.java @@ -5,7 +5,7 @@ public class BlackTile extends PlaceableElement { public BlackTile() { super( - "NURI-PLAC-0001", + "NURI-ELEM-0001", "Black Tile", "The black tile", "edu/rpi/legup/images/nurikabe/tiles/BlackTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/NumberTile.java b/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/NumberTile.java index 475b278da..2015d990b 100644 --- a/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/NumberTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/NumberTile.java @@ -1,13 +1,13 @@ package edu.rpi.legup.puzzle.nurikabe.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class NumberTile extends NonPlaceableElement { +public class NumberTile extends PlaceableElement { private int object_num; public NumberTile() { super( - "NURI-UNPL-0001", + "NURI-ELEM-0002", "Number Tile", "A numbered tile", "edu/rpi/legup/images/nurikabe/tiles/NumberTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/UnknownTile.java b/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/UnknownTile.java index 85d47e208..8a18c80cc 100644 --- a/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/UnknownTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/UnknownTile.java @@ -1,11 +1,11 @@ package edu.rpi.legup.puzzle.nurikabe.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class UnknownTile extends NonPlaceableElement { +public class UnknownTile extends PlaceableElement { public UnknownTile() { super( - "NURI-UNPL-0002", + "NURI-ELEM-0003", "Unknown Tile", "A blank tile", "edu/rpi/legup/images/nurikabe/tiles/UnknownTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/WhiteTile.java b/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/WhiteTile.java index 35eb63b81..ae07c6d76 100644 --- a/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/WhiteTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/WhiteTile.java @@ -5,7 +5,7 @@ public class WhiteTile extends PlaceableElement { public WhiteTile() { super( - "NURI-PLAC-0002", + "NURI-ELEM-0004", "White Tile", "The white tile", "edu/rpi/legup/images/nurikabe/tiles/WhiteTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/nurikabe_elements_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/nurikabe_elements_reference_sheet.txt new file mode 100644 index 000000000..667972fd6 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/nurikabe/elements/nurikabe_elements_reference_sheet.txt @@ -0,0 +1,4 @@ +NURI-ELEM-0001 : BlackTile +NURI-ELEM-0002 : NumberTile +NURI-ELEM-0003 : UnknownTile +NURI-ELEM-0004 : WhiteTile diff --git a/src/main/java/edu/rpi/legup/puzzle/nurikabe/rules/BlackOrWhiteCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/nurikabe/rules/BlackOrWhiteCaseRule.java index ac0ab6df6..1c87d5cfa 100644 --- a/src/main/java/edu/rpi/legup/puzzle/nurikabe/rules/BlackOrWhiteCaseRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/nurikabe/rules/BlackOrWhiteCaseRule.java @@ -24,7 +24,7 @@ public BlackOrWhiteCaseRule() { /** * Checks whether the {@link TreeTransition} logically follows from the parent node using this - * rule. This method is the one that should overridden in child classes. + * rule. This method is the one that should be overridden in child classes. * * @param transition transition to check * @return null if the child node logically follow from the parent node, otherwise error message @@ -84,6 +84,10 @@ public CaseBoard getCaseBoard(Board board) { @Override public ArrayList getCases(Board board, PuzzleElement puzzleElement) { ArrayList cases = new ArrayList<>(); + if (puzzleElement == null) { + return cases; + } + Board case1 = board.copy(); PuzzleElement data1 = case1.getPuzzleElement(puzzleElement); data1.setData(NurikabeType.WHITE.toValue()); diff --git a/src/main/java/edu/rpi/legup/puzzle/nurikabe/rules/FinishRoomCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/nurikabe/rules/FinishRoomCaseRule.java index 4901cfa6f..025212d96 100644 --- a/src/main/java/edu/rpi/legup/puzzle/nurikabe/rules/FinishRoomCaseRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/nurikabe/rules/FinishRoomCaseRule.java @@ -19,6 +19,7 @@ public class FinishRoomCaseRule extends CaseRule { private int legitCases = 0; // placeholder for amount of cases originally generated in case user tries to delete + private Set uniqueCases; // stores the unique case hashes // cases @@ -26,15 +27,16 @@ public FinishRoomCaseRule() { super( "NURI-CASE-0002", "Finish Room", - "Room can be finished in up to five ways", + "Room can be finished in up to nine ways", "edu/rpi/legup/images/nurikabe/cases/FinishRoom.png"); - this.MAX_CASES = 5; - this.MIN_CASES = 2; + this.MAX_CASES = 9; + this.MIN_CASES = 1; + this.uniqueCases = new HashSet<>(); } /** * Checks whether the {@link TreeTransition} logically follows from the parent node using this - * rule. This method is the one that should overridden in child classes. + * rule. This method is the one that should have overridden in child classes. * * @param transition transition to check * @return null if the child node logically follow from the parent node, otherwise error message @@ -43,18 +45,18 @@ public FinishRoomCaseRule() { public String checkRuleRaw(TreeTransition transition) { NurikabeBoard destBoardState = (NurikabeBoard) transition.getBoard(); List childTransitions = transition.getParents().get(0).getChildren(); - if (childTransitions.size() > 5) { + if (childTransitions.size() > MAX_CASES) { return super.getInvalidUseOfRuleMessage() - + ": This case rule must have 5 or less children."; + + ": This case rule must have 9 or less children."; } - if (childTransitions.size() < 2) { + if (childTransitions.size() < MIN_CASES) { return super.getInvalidUseOfRuleMessage() - + ": This case rule must have 2 or more children."; + + ": This case rule must have 1 or more children."; } if (childTransitions.size() != legitCases) { return super.getInvalidUseOfRuleMessage() + ": Cases can not be removed from the branch."; - } // stops user from deleting 1 or mose generated cases and still having path show as green + } // stops user from deleting 1 or more generated cases and still having path show as green Set locations = new HashSet<>(); for (TreeTransition t1 : childTransitions) { locations.add( @@ -94,21 +96,18 @@ public CaseBoard getCaseBoard(Board board) { boolean only = true; // placeholder boolean of if the element being tested is the only // number block in the room or not + for (NurikabeCell d : disRow) { // loops through tiles in the room + // if found another number tile and it's data is different from the element + // we're working with if ((d.getType() == NurikabeType.NUMBER) - && !(d.getData() - .equals( - ((NurikabeCell) element) - .getData()))) { // if found another number tile - // and it's data is different - // than the element we're - // working with - only = false; // set only to false + && !(d.getData().equals(((NurikabeCell) element).getData()))) { + only = false; } } - if (disRow.size() + 1 == ((NurikabeCell) element).getData() - && only) { // if size of region is 1 less than the number block and the - // number block is only number block in the region + // if size of region is 1 less than the number block and the number block is only + // number block in the region + if (disRow.size() < ((NurikabeCell) element).getData() && only) { caseBoard.addPickableElement(element); // add that room as a pickable element } } @@ -126,16 +125,21 @@ public CaseBoard getCaseBoard(Board board) { @Override public ArrayList getCases(Board board, PuzzleElement puzzleElement) { ArrayList cases = new ArrayList<>(); // makes array list of cases + if (puzzleElement == null) { + return cases; + } + NurikabeBoard nuriBoard = (NurikabeBoard) board.copy(); // nurikabe board to edit - NurikabeCell numbaCell = + NurikabeCell numberCell = nuriBoard.getCell( ((NurikabeCell) puzzleElement).getLocation().x, ((NurikabeCell) puzzleElement) .getLocation() .y); // number cell whose room we want to fill - int filledRoomSize = numbaCell.getData(); // size of room we want afterward - Set locations = - new HashSet<>(); // locations where white space is added to finish room + + Point origPoint = new Point(numberCell.getLocation().x, numberCell.getLocation().y); + int filledRoomSize = numberCell.getData(); // size of room we want afterward + Point left = new Point(-1, 0); Point right = new Point(1, 0); Point bot = new Point(0, -1); @@ -145,92 +149,147 @@ public ArrayList getCases(Board board, PuzzleElement puzzleElement) { directions.add(right); directions.add(top); directions.add(bot); + Set checkedPoints = new HashSet<>(); // add all into checked points and continue at start of loop if // inside DisjointSets regions = NurikabeUtilities.getNurikabeRegions(nuriBoard); // gathers regions - Set disRow = regions.getSet(numbaCell); // set of white spaces - for (NurikabeCell d : disRow) { // loops through white spaces - if (cases.size() >= 6) { // no need to check this many cases - // throw new IllegalStateException("Too many cases"); - continue; // crash/runtime protection + Set numberCellRegion = regions.getSet(numberCell); // set of white spaces + + for (NurikabeCell d : numberCellRegion) { // loops through white spaces + generateCases( + nuriBoard, d, filledRoomSize, directions, checkedPoints, cases, origPoint); + } + + legitCases = cases.size(); + return cases; + } + + /** + * Recursively generates possible cases for filling a room with white cells based on the current + * board state and specified parameters. + * + * @param nuriBoard the current Nurikabe board state + * @param currentCell the current cell being evaluated + * @param filledRoomSize the target size for the room being filled + * @param directions the set of possible directions to expand the room + * @param checkedPoints the set of points already evaluated to avoid redundancy + * @param cases the list of valid board cases generated + * @param origPoint the original point of the number cell initiating the room filling + */ + private void generateCases( + NurikabeBoard nuriBoard, + NurikabeCell currentCell, + int filledRoomSize, + Set directions, + Set checkedPoints, + ArrayList cases, + Point origPoint) { + for (Point direction : directions) { + Point newPoint = + new Point( + currentCell.getLocation().x + direction.x, + currentCell.getLocation().y + direction.y); + + if (newPoint.x < 0 + || newPoint.y < 0 + || newPoint.x >= nuriBoard.getWidth() + || newPoint.y >= nuriBoard.getHeight()) { + continue; // out of bounds } - for (Point direction : directions) { - if (cases.size() >= 6) { // no need to check this many cases - // throw new IllegalStateException("Too many cases"); - continue; // crash/runtime protection - } - if (!((nuriBoard.getWidth() > (d.getLocation().x + direction.x) - && (nuriBoard.getHeight() > d.getLocation().y + direction.y) - && (d.getLocation().x + direction.x >= 0) - && (d.getLocation().y + direction.y >= 0)))) { - continue; // if next location check would be outside of grid then continue - } - NurikabeCell curr = - nuriBoard.getCell( - d.getLocation().x + direction.x, d.getLocation().y + direction.y); - if (checkedPoints.contains(curr.getLocation())) { - continue; // if we already checked whether or not making this tile white would - // complete the room then continue - } - checkedPoints.add( - curr.getLocation()); // adds location to checkedPoints so we don't check - // it again and accidentally add - if (curr.getType() - == NurikabeType - .UNKNOWN) { // found adjacent space to region that is currently - // unknown - curr.setData( - NurikabeType.WHITE.toValue()); // changes adjacent cell color to white - nuriBoard.addModifiedData(curr); // adds modified before check - regions = NurikabeUtilities.getNurikabeRegions(nuriBoard); // update regions - Set disCreatedRow = - regions.getSet( - curr); // gets set of created row with new white cell added - if (disCreatedRow.size() - == filledRoomSize) { // If adding white fills the room to exact size of - // number block and doesn't connect with another - // room - Point here = curr.getLocation(); // gets current location of new white tile - // that fills room - boolean alreadyIn = - false; // sets whether or not the tile has already been added to - // false - for (Point p : - locations) { // loops through locations of previously added tiles - if (p == here) { // if point is already in - alreadyIn = true; // change already in to true + + NurikabeCell newCell = nuriBoard.getCell(newPoint.x, newPoint.y); + if (checkedPoints.contains(newPoint)) { + continue; // already checked + } + + if (newCell.getType() == NurikabeType.UNKNOWN) { + newCell.setData( + NurikabeType.WHITE.toValue()); // changes adjacent cell color to white + newCell.setModifiable(false); + checkedPoints.add(newPoint); + + DisjointSets regions = + NurikabeUtilities.getNurikabeRegions(nuriBoard); // update regions variable + Set newRoomSet = + regions.getSet( + newCell); // gets set of cells in room with new white cell added + + if (!touchesDifferentRoom( + nuriBoard, newCell, filledRoomSize, directions, origPoint)) { + if (newRoomSet.size() + == filledRoomSize) { // if adding white fills the room to exact size of + // number block and doesn't connect with another room + Board caseBoard = nuriBoard.copy(); + // check if case for board already exists + boolean unique = true; + for (Board board : cases) { + if (caseBoard.equalsBoard(board)) { + unique = false; break; } } - if (!alreadyIn) { // if point wasn't already in - Board casey = - nuriBoard.copy(); // copy the current board with white tile - // changed - PuzzleElement datacasey = - curr; // gets changed white tile as a puzzle element - datacasey.setData( - NurikabeType.WHITE - .toValue()); // ensure set to white, probably redundant - casey.addModifiedData(datacasey); // ensure confirmed white change - regions = - NurikabeUtilities.getNurikabeRegions( - nuriBoard); // update regions - cases.add(casey); // add this case to list of cases - locations.add( - here); // add location of new white tile to list of locations so - // that we don't accidentally add it again later + if (unique) { + caseBoard.addModifiedData(newCell); + cases.add(caseBoard); } + } else if (newRoomSet.size() < filledRoomSize) { + generateCases( + nuriBoard, + newCell, + filledRoomSize, + directions, + checkedPoints, + cases, + origPoint); } - curr.setData(NurikabeType.UNKNOWN.toValue()); // set cell type back to unknown - nuriBoard.addModifiedData(curr); // confirms change back to unknown - regions = NurikabeUtilities.getNurikabeRegions(nuriBoard); // updates regions } + newCell.setData(NurikabeType.UNKNOWN.toValue()); + newCell.setModifiable(true); + checkedPoints.remove(newPoint); } - legitCases = cases.size(); } - return cases; + } + + /** + * Determines if a given cell touches a different room by checking adjacent cells in specified + * directions. + * + * @param board the current Nurikabe board state + * @param cell the cell being evaluated + * @param origRoomSize the size of the original room being filled + * @param directions the set of possible directions to check around the cell + * @param origPoint the original point of the number cell initiating the room filling + * @return true if the cell touches a different room, false otherwise + */ + private boolean touchesDifferentRoom( + NurikabeBoard board, + NurikabeCell cell, + int origRoomSize, + Set directions, + Point origPoint) { + for (Point direction : directions) { + Point adjacentPoint = + new Point( + cell.getLocation().x + direction.x, cell.getLocation().y + direction.y); + + if (adjacentPoint.x >= 0 + && adjacentPoint.y >= 0 + && adjacentPoint.x < board.getWidth() + && adjacentPoint.y < board.getHeight()) { // check if out of bounds + NurikabeCell adjacentCell = board.getCell(adjacentPoint.x, adjacentPoint.y); + // check if the adjacent cell is a number cell + if (adjacentCell.getType() == NurikabeType.NUMBER) { + // check if it's different from the original number cell + if (origRoomSize != adjacentCell.getData() + || (adjacentPoint.x != origPoint.x || adjacentPoint.y != origPoint.y)) { + return true; + } + } + } + } + return false; } /** diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableCell.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableCell.java index 75bba369f..ffd7c491d 100644 --- a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableCell.java +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableCell.java @@ -135,22 +135,22 @@ public void setType(Element e, MouseEvent m) { } // Red Element - if (e.getElementID().equals("STTT-PLAC-0002")) { + if (e.getElementID().equals("STTT-ELEM-0004")) { this.data = ShortTruthTableCellType.FALSE; } // Green Element else { - if (e.getElementID().equals("STTT-PLAC-0001")) { + if (e.getElementID().equals("STTT-ELEM-0002")) { this.data = ShortTruthTableCellType.TRUE; } // Unknown Element else { - if (e.getElementID().equals("STTT-PLAC-0003")) { + if (e.getElementID().equals("STTT-ELEM-0005")) { this.data = ShortTruthTableCellType.UNKNOWN; } // Argument Element else { - if (e.getElementID().equals("STTT-UNPL-0001")) { + if (e.getElementID().equals("STTT-ELEM-0001")) { // Prevents non-argument symbols from being changed if (!(this.symbol >= 'A' && this.symbol <= 'Z')) { return; @@ -172,7 +172,7 @@ public void setType(Element e, MouseEvent m) { } // And/Or Element else { - if (e.getElementID().equals("STTT-UNPL-0002")) { + if (e.getElementID().equals("STTT-ELEM-0003")) { if (m.getButton() == MouseEvent.BUTTON1) { if (this.symbol == '^') { this.symbol = '|'; diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/ArgumentElement.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/ArgumentElement.java index 9294fba4e..912fd2672 100644 --- a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/ArgumentElement.java +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/ArgumentElement.java @@ -1,11 +1,11 @@ package edu.rpi.legup.puzzle.shorttruthtable.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class ArgumentElement extends NonPlaceableElement { +public class ArgumentElement extends PlaceableElement { public ArgumentElement() { super( - "STTT-UNPL-0001", + "STTT-ELEM-0001", "Argument Element", "Argument of logic statement element", "edu/rpi/legup/images/shorttruthtable/tiles/LetterTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/GreenElement.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/GreenElement.java index 783186baa..56221fef3 100644 --- a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/GreenElement.java +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/GreenElement.java @@ -5,7 +5,7 @@ public class GreenElement extends PlaceableElement { public GreenElement() { super( - "STTT-PLAC-0001", + "STTT-ELEM-0002", "Green Element", "A green tile to set certain tiles to true", "edu/rpi/legup/images/shorttruthtable/tiles/GreenTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/LogicSymbolElement.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/LogicSymbolElement.java index 5fed4b1df..b82ebc2cb 100644 --- a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/LogicSymbolElement.java +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/LogicSymbolElement.java @@ -1,11 +1,11 @@ package edu.rpi.legup.puzzle.shorttruthtable.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class LogicSymbolElement extends NonPlaceableElement { +public class LogicSymbolElement extends PlaceableElement { public LogicSymbolElement() { super( - "STTT-UNPL-0002", + "STTT-ELEM-0003", "Logic Symbol Element", "Logic symbol element", "edu/rpi/legup/images/shorttruthtable/tiles/ConditionalBiconditionalTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/RedElement.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/RedElement.java index e2a589b65..2114e62ec 100644 --- a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/RedElement.java +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/RedElement.java @@ -5,7 +5,7 @@ public class RedElement extends PlaceableElement { public RedElement() { super( - "STTT-PLAC-0002", + "STTT-ELEM-0004", "Red Element", "A red tile to set certain tiles to false", "edu/rpi/legup/images/shorttruthtable/tiles/RedTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/UnknownElement.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/UnknownElement.java index d475bc05d..52b54f202 100644 --- a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/UnknownElement.java +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/UnknownElement.java @@ -5,7 +5,7 @@ public class UnknownElement extends PlaceableElement { public UnknownElement() { super( - "STTT-PLAC-0003", + "STTT-ELEM-0005", "Unknown Element", "A blank tile", "edu/rpi/legup/images/shorttruthtable/tiles/UnknownTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/shorttruthtable_elements_reference_sheet b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/shorttruthtable_elements_reference_sheet index 471631553..c5421169f 100644 --- a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/shorttruthtable_elements_reference_sheet +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/shorttruthtable_elements_reference_sheet @@ -1,6 +1,5 @@ -STTT-UNPL-0001 : ArgumentElement -STTT-UNPL-0002 : ConditionalBiconditionalElement - -STTT-PLAC-0001 : GreenElement -STTT-PLAC-0002 : RedElement -STTT-PLAC-0003 : UnknownElement \ No newline at end of file +STTT-ELEM-0001 : ArgumentElement +STTT-ELEM-0002 : GreenElement +STTT-ELEM-0003 : LogicSymbolElement +STTT-ELEM-0004 : RedElement +STTT-ELEM-0005 : UnknownElement \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/rules/caserule/CaseRuleAtomic.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/rules/caserule/CaseRuleAtomic.java index 58d2068b2..22b49fd77 100644 --- a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/rules/caserule/CaseRuleAtomic.java +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/rules/caserule/CaseRuleAtomic.java @@ -44,6 +44,9 @@ public CaseBoard getCaseBoard(Board board) { @Override public ArrayList getCases(Board board, PuzzleElement puzzleElement) { ArrayList cases = new ArrayList<>(); + if (puzzleElement == null) { + return cases; + } Board case1 = board.copy(); PuzzleElement data1 = case1.getPuzzleElement(puzzleElement); diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/rules/caserule/CaseRule_GenericStatement.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/rules/caserule/CaseRule_GenericStatement.java index 99f771246..8aeb51a46 100644 --- a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/rules/caserule/CaseRule_GenericStatement.java +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/rules/caserule/CaseRule_GenericStatement.java @@ -85,6 +85,11 @@ public CaseBoard getCaseBoard(Board board) { @SuppressWarnings("unchecked") @Override public ArrayList getCases(Board board, PuzzleElement puzzleElement) { + + if (puzzleElement == null) { + return new ArrayList(); + } + ShortTruthTableBoard sttBoard = ((ShortTruthTableBoard) board); ShortTruthTableCell cell = sttBoard.getCellFromElement(puzzleElement); diff --git a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/Skyscrapers.java b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/Skyscrapers.java index df5ba78a3..44f416cef 100644 --- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/Skyscrapers.java +++ b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/Skyscrapers.java @@ -46,7 +46,7 @@ public Board generatePuzzle(int difficulty) { * @return true if the given dimensions are valid for Skyscrapers, false otherwise */ public boolean isValidDimensions(int rows, int columns) { - return rows >= 4 && rows == columns; + return rows >= 3 && rows == columns; } /** diff --git a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersBoard.java b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersBoard.java index 4cd09b254..0fc133786 100644 --- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersBoard.java +++ b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersBoard.java @@ -209,7 +209,7 @@ public void setCell(int x, int y, Element e, MouseEvent m) { SkyscrapersClue clue = this.getClue(x, y); if (e == null) return; if (clue != null) { - if (!e.getElementID().equals("SKYS-UNPL-0003")) { + if (!e.getElementID().equals("SKYS-ELEM-0001")) { return; } @@ -217,10 +217,10 @@ public void setCell(int x, int y, Element e, MouseEvent m) { if (clue.getData() < dimension.height) { clue.setData(clue.getData() + 1); } else { - clue.setData(0); + clue.setData(1); } } else { - if (clue.getData() > 0) { + if (clue.getData() > 1) { clue.setData(clue.getData() - 1); } else { clue.setData(dimension.height); diff --git a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersCell.java b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersCell.java index 1cf9a357b..9e7283b20 100644 --- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersCell.java +++ b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersCell.java @@ -27,10 +27,10 @@ public SkyscrapersType getType() { @Override public void setType(Element e, MouseEvent m) { switch (e.getElementID()) { - case "SKYS-UNPL-0001": + case "SKYS-ELEM-0002": this.data = 0; break; - case "SKYS-UNPL-0002": + case "SKYS-ELEM-0001": switch (m.getButton()) { case MouseEvent.BUTTON1: if (this.data <= 0 || this.data >= this.max) { diff --git a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersClueView.java b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersClueView.java index 5a49a1476..cfb3388ff 100644 --- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersClueView.java +++ b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersClueView.java @@ -25,11 +25,11 @@ public SkyscrapersClue getPuzzleElement() { @Override public void draw(Graphics2D graphics2D) { drawElement(graphics2D); + if (this.isHover()) { + drawHover(graphics2D); + } if (this.isShowCasePicker() && this.isCaseRulePickable()) { drawCase(graphics2D); - if (this.isHover()) { - drawHover(graphics2D); - } } } @@ -54,6 +54,22 @@ public void drawElement(Graphics2D graphics2D) { int xText = location.x + (size.width - metrics.stringWidth(value)) / 2; int yText = location.y + ((size.height - metrics.getHeight()) / 2) + metrics.getAscent(); + + // REPRESENT NO CLUE AS EMPTY STRING INSTEAD OF 0, SOLVING PUZZLES WITH NO CLUE IS CURRENTLY + // NOT WORKING + // IF YOU ARE IMPLEMENTING NO CLUE FUNCTIONALITY, UNCOMMENT BELOW CODE AND DELETE OTHER IF + // STATEMENT, + // ADDITIONALLY, GO TO SkyscrapersBoard AND EDIT LINES 220 AND 223 SO YOU CAN CYCLE FOR NO + // CLUE + // IN THE SKYSCRAPERS PUZZLE EDITOR + // if (value.equals("0")) { + // value = ""; + // } + if (value.equals("0")) { + value = "1"; + clue.setData(1); + } + graphics2D.drawString(value, xText, yText); } } diff --git a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/elements/ClueTile.java b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/elements/ClueTile.java deleted file mode 100644 index 64c9033e6..000000000 --- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/elements/ClueTile.java +++ /dev/null @@ -1,14 +0,0 @@ -package edu.rpi.legup.puzzle.skyscrapers.elements; - -import edu.rpi.legup.model.elements.NonPlaceableElement; - -public class ClueTile extends NonPlaceableElement { - - public ClueTile() { - super( - "SKYS-UNPL-0003", - "Clue Tile", - "Clue Updater", - "edu/rpi/legup/images/skyscrapers/tiles/ClueTile.png"); - } -} diff --git a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/elements/NumberTile.java b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/elements/NumberTile.java index 4d6b37c9a..f60e5fe8b 100644 --- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/elements/NumberTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/elements/NumberTile.java @@ -1,11 +1,11 @@ package edu.rpi.legup.puzzle.skyscrapers.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class NumberTile extends NonPlaceableElement { +public class NumberTile extends PlaceableElement { public NumberTile() { super( - "SKYS-UNPL-0002", + "SKYS-ELEM-0001", "Number Tile", "A numbered tile", "edu/rpi/legup/images/skyscrapers/tiles/ClueTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/elements/UnknownTile.java b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/elements/UnknownTile.java index 2fb21193a..07f6a1238 100644 --- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/elements/UnknownTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/elements/UnknownTile.java @@ -1,11 +1,11 @@ package edu.rpi.legup.puzzle.skyscrapers.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class UnknownTile extends NonPlaceableElement { +public class UnknownTile extends PlaceableElement { public UnknownTile() { super( - "SKYS-UNPL-0001", + "SKYS-ELEM-0002", "Unknown", "A blank tile", "edu/rpi/legup/images/skyscrapers/tiles/UnknownTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/elements/skyscrapers_elements_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/elements/skyscrapers_elements_reference_sheet.txt index 604e1824e..14e76a29d 100644 --- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/elements/skyscrapers_elements_reference_sheet.txt +++ b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/elements/skyscrapers_elements_reference_sheet.txt @@ -1,3 +1,2 @@ -SKYS-UNPL-0001: Unknown Tile -SKYS-UNPL-0002: Number Tile -SKYS-UNPL-0003: Clue "Tile" \ No newline at end of file +SKYS-ELEM-0001: NumberTile +SKYS-ELEM-0002: UnknownTile \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/rules/CellForNumberCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/rules/CellForNumberCaseRule.java index 45bdadea3..b48962c41 100644 --- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/rules/CellForNumberCaseRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/rules/CellForNumberCaseRule.java @@ -61,6 +61,9 @@ public CaseBoard getCaseBoard(Board board) { public ArrayList getCasesFor(Board board, PuzzleElement puzzleElement, Integer number) { ArrayList cases = new ArrayList<>(); + if (puzzleElement == null) { + return cases; + } SkyscrapersClue clue = (SkyscrapersClue) puzzleElement; SkyscrapersBoard skyscrapersboard = (SkyscrapersBoard) board; diff --git a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/rules/NumberForCellCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/rules/NumberForCellCaseRule.java index 145dd6ee2..4f8e1df6b 100644 --- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/rules/NumberForCellCaseRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/rules/NumberForCellCaseRule.java @@ -47,6 +47,9 @@ public CaseBoard getCaseBoard(Board board) { @Override public ArrayList getCases(Board board, PuzzleElement puzzleElement) { ArrayList cases = new ArrayList<>(); + if (puzzleElement == null) { + return cases; + } SkyscrapersCell cell = (SkyscrapersCell) puzzleElement; SkyscrapersBoard skyscrapersboard = (SkyscrapersBoard) board; diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/allfiles.txt b/src/main/java/edu/rpi/legup/puzzle/starbattle/allfiles.txt deleted file mode 100644 index 5a9ec0f0a..000000000 --- a/src/main/java/edu/rpi/legup/puzzle/starbattle/allfiles.txt +++ /dev/null @@ -1,235 +0,0 @@ -//StarBattle.java - -package edu.rpi.legup.puzzle.starbattle; - -import edu.rpi.legup.model.Puzzle; -import edu.rpi.legup.model.gameboard.Board; - -public class StarBattle extends Puzzle { - public StarBattle() { - super(); - this.name = "StarBattle"; - - this.importer = new StarBattleImporter(this); - this.exporter = new StarBattleExporter(this); - - this.factory = new StarBattleCellFactory(); - } - - @Override - public void initializeView() { - } - - @Override - public Board generatePuzzle(int difficulty) { - return null; - } - - @Override - public boolean isBoardComplete(Board board) { - return true; - } - - @Override - public void onBoardChange(Board board) { - } -} - -//StarBattleBoard.java - -package edu.rpi.legup.puzzle.lightup; - -import edu.rpi.legup.model.gameboard.GridBoard; -import edu.rpi.legup.model.gameboard.PuzzleElement; - -import java.awt.*; -import java.util.HashSet; -import java.util.Set; - -public class StarBattleBoard extends GridBoard { - - private int size; - private vector group_sizes; - - /** - * StarBattleBoard Constructor - create a new Star Battle board - * - * @param size size of one side of the star battle board - */ - - public StarBattleBoard(int size) { - super(size, size); - group_sizes = vector(size); - } - - @Override - public StarBattleCell getCell(int x, int y) { - return (StarBattleCell) super.getCell(x, y); - } - - -} - -//StarBattleCell.java - -package edu.rpi.legup.puzzle.starbattle; - -import edu.rpi.legup.model.gameboard.GridCell; - -import java.awt.*; -import java.util.HashSet; -import java.util.Set; - -public class StarBattleCell extends GridCell { - private int groupIndex; - private int max; - - /** - * StarBattleCell Constructor - creates a new StarBattle cell to hold the puzzleElement - * - * @param valueInt value of the star battle cell denoting its state - * @param location location of the cell on the board - * @param size size of the star battle cell - */ - public StarBattleCell(int value, Point location, int groupIndex, int size) { - super(value, location); - this.groupIndex = groupIndex; - this.max = size; - } - - @Override - public void setType(Element e, MouseEvent m) { - switch (e.getElementID()) { - case "SBUP-PLAC-0001": - this.data = -3; - break; - case "SBUP-PLAC-0002": - this.data = -2; - break; - case "SBUP-PLAC-0003": - this.data = -1; - break; - case "SBUP-UNPL-0001"://Not sure how button events work - switch (m.getButton()){ - case MouseEvent.BUTTON1: - if (this.data < 0 || this.data > 3) { - this.data = 0; - } - else { - this.data = this.data + 1; - } - break; - case MouseEvent.BUTTON3: - if (this.data > 0) { - this.data = this.data - 1; - } - else { - this.data = 3;//Unsure - } - break; - } - break; - } - } - - public LightUpCellType getType() { - switch (data) { - case -3: - return LightUpCellType.UNKNOWN; - case -2: - return LightUpCellType.STAR; - case -1: - return LightUpCellType.BLACK; - default: - if (data >= 0) { - return StarBattleCellType.WHITE; - } - } - return null; - } - - /** - * Gets the region index of the cell - * - * @return group index of the cell - */ - public int getGroupIndex() { - return groupIndex; - } - - /** - * Gets the size of the cell - * - * @return size of the cell - */ - - public int getMax() { - return max; - } - -} - -//StarBattleCellController.java - -package edu.rpi.legup.puzzle.starbattle; - -import edu.rpi.legup.controller.ElementController; -import edu.rpi.legup.model.gameboard.PuzzleElement; - -import java.awt.event.MouseEvent; - -public class StarBattleCellController extends ElementController { - @Override - public void changeCell(MouseEvent e, PuzzleElement data) { - StarBattleCell cell = (StarBattleCell) data; - if (e.getButton() == MouseEvent.BUTTON1) { - if (e.isControlDown()) { - this.boardView.getSelectionPopupMenu().show(boardView, this.boardView.getCanvas().getX() + e.getX(), this.boardView.getCanvas().getY() + e.getY()); - } - else { - if (cell.getData() == 0) { - data.setData(-3); - } - else { - data.setData(cell.getData() + 1); - } - } - } - else { - if (e.getButton() == MouseEvent.BUTTON3) { - if (cell.getData() == -3) { - data.setData(0); - } - else { - data.setData(cell.getData() - 1); - } - } - } - } -} - -//StarBattleCellFactory.java - - - -//StarBattleCellType.java -package edu.rpi.legup.puzzle.starbattle; - -public enum StarBattleType { - UNKNOWN(-3), STAR(-2), BLACK(-1), WHITE(0); - - public int value; - - StarBattleCell(int value) { - this.value = value; - } -} - -//StarBattleExporter.java -//StarBattleImporter.java -//StarBattleView.java - -How to run Legup: - -./gradlew build -Java -jar build/libs/Legup.jar \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/BlackTile.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/BlackTile.java index 99f42886e..c4bbf7297 100644 --- a/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/BlackTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/BlackTile.java @@ -1,8 +1,8 @@ package edu.rpi.legup.puzzle.starbattle.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class BlackTile extends NonPlaceableElement { +public class BlackTile extends PlaceableElement { public BlackTile() { super( "STBL-PLAC-0002", diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/StarTile.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/StarTile.java index 13ada3f4d..793d4dbeb 100644 --- a/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/StarTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/StarTile.java @@ -1,8 +1,8 @@ package edu.rpi.legup.puzzle.starbattle.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class StarTile extends NonPlaceableElement { +public class StarTile extends PlaceableElement { public StarTile() { super( "STBL-PLAC-0001", diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/UnknownTile.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/UnknownTile.java index 425fb5d5e..30921de8d 100644 --- a/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/UnknownTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/UnknownTile.java @@ -1,8 +1,8 @@ package edu.rpi.legup.puzzle.starbattle.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class UnknownTile extends NonPlaceableElement { +public class UnknownTile extends PlaceableElement { public UnknownTile() { super( "STBL-UNPL-0001", diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/starbattle_elements_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/starbattle_elements_reference_sheet.txt new file mode 100644 index 000000000..82352bd04 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/starbattle_elements_reference_sheet.txt @@ -0,0 +1,4 @@ +STBL-ELEM-0001 : BlackTile +STBL-ELEM-0002 : StarTile +STBL-ELEM-0003 : UnknownTile +STBL-ELEM-0004 : WhiteTile \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/StarOrEmptyCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/StarOrEmptyCaseRule.java index df900dcd5..efd86bd7b 100644 --- a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/StarOrEmptyCaseRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/StarOrEmptyCaseRule.java @@ -84,6 +84,10 @@ public CaseBoard getCaseBoard(Board board) { @Override public ArrayList getCases(Board board, PuzzleElement puzzleElement) { ArrayList cases = new ArrayList<>(); + if (puzzleElement == null) { + return cases; + } + Board case1 = board.copy(); PuzzleElement data1 = case1.getPuzzleElement(puzzleElement); data1.setData(StarBattleCellType.STAR.value); diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/starbattle_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/starbattle_reference_sheet.txt deleted file mode 100644 index f18965fd6..000000000 --- a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/starbattle_reference_sheet.txt +++ /dev/null @@ -1,19 +0,0 @@ -Case Rules: -Add Star: STBL-CASE-0001 -Star or Empty: STBL-CASE-0002 - -Basic Rules: -Blackout: STBL-BASC-0001 -Columns Within Regions: STBL-BASC-0002 -Columns Within Rows: STBL-BASC-0003 -Finish With Stars: STBL-BASC-0004 -Regions Within Columns: STBL-BASC-0005 -Regions Within Rows: STBL-BASC-0006 -Rows Within Columns: STBL-BASC-0007 -Rows Within Regions: STBL-BASC-0008 -Surround Star: STBL-BASC-0009 - -Contradiction Rules: -Too Many Stars: STBL-CONT-0001 -Too Few Stars: STBL-CONT-0002 -Clashing Orbit: STBL-CONT-0003 \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/ModelSudokuBoard.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/ModelSudokuBoard.java new file mode 100644 index 000000000..f7893ca32 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/ModelSudokuBoard.java @@ -0,0 +1,17 @@ +package edu.rpi.legup.puzzle.sudoku; + +public class ModelSudokuBoard { + public int getModelRegionNumbers(int index) { + int columnMod = index % 3 + 1; + int rowMod = ((index / 9) % 3) * 3; + return columnMod + rowMod; + } + + public int getModelRowNumbers(int index) { + return index % 9 + 1; + } + + public int getModelColumnNumbers(int index) { + return index / 9 + 1; + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/PossibleNumberCaseBoard.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/PossibleNumberCaseBoard.java index 0b6971235..c5f9eec2a 100644 --- a/src/main/java/edu/rpi/legup/puzzle/sudoku/PossibleNumberCaseBoard.java +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/PossibleNumberCaseBoard.java @@ -2,7 +2,7 @@ import edu.rpi.legup.model.gameboard.CaseBoard; import edu.rpi.legup.model.gameboard.PuzzleElement; -import edu.rpi.legup.puzzle.sudoku.rules.PossibleNumberCaseRule; +import edu.rpi.legup.puzzle.sudoku.rules.PossibleCellsForNumberRegionCaseRule; import java.awt.event.MouseEvent; import java.util.HashSet; import java.util.Set; @@ -15,7 +15,7 @@ public class PossibleNumberCaseBoard extends CaseBoard { private Set pickableCols; public PossibleNumberCaseBoard( - SudokuBoard baseBoard, PossibleNumberCaseRule caseRule, SudokuCell cell) { + SudokuBoard baseBoard, PossibleCellsForNumberRegionCaseRule caseRule, SudokuCell cell) { super(baseBoard, caseRule); this.cell = cell; this.pickableRegions = new HashSet<>(); diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/Sudoku.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/Sudoku.java index 877c92665..c27269536 100644 --- a/src/main/java/edu/rpi/legup/puzzle/sudoku/Sudoku.java +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/Sudoku.java @@ -29,6 +29,8 @@ public BoardView getBoardView() { @Override public void initializeView() { boardView = new SudokuView((SudokuBoard) currentBoard); + boardView.setBoard(currentBoard); + addBoardListener(boardView); } /** diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuCell.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuCell.java index 006e6c0a5..75e5820a0 100644 --- a/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuCell.java +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuCell.java @@ -1,7 +1,9 @@ package edu.rpi.legup.puzzle.sudoku; +import edu.rpi.legup.model.elements.Element; import edu.rpi.legup.model.gameboard.GridCell; import java.awt.*; +import java.awt.event.MouseEvent; import java.util.HashSet; import java.util.Set; @@ -59,4 +61,32 @@ public SudokuCell copy() { copy.setGiven(isGiven); return copy; } + + /** + * Sets the type of this NurikabeCell + * + * @param e element to set the type of this nurikabe cell to + */ + @Override + public void setType(Element e, MouseEvent m) { + if (e.getElementName().equals("Number Tile")) { + if (m.getButton() == MouseEvent.BUTTON1) { + if (this.data <= 0 || this.data > 8) { + this.data = 1; + } else { + this.data = this.data + 1; + } + } else { + if (m.getButton() == MouseEvent.BUTTON3) { + if (this.data > 1) { + this.data = this.data - 1; + } else { + this.data = 9; + } + } + } + } else if (e.getElementName().equals("Unknown Tile")) { + this.data = 0; + } + } } diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuCellController.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuCellController.java index 9b24f13da..bcad1a0ce 100644 --- a/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuCellController.java +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuCellController.java @@ -8,7 +8,7 @@ public class SudokuCellController extends ElementController { @Override public void changeCell(MouseEvent e, PuzzleElement data) { SudokuCell cell = (SudokuCell) data; - System.out.print(111); + if (e.getButton() == MouseEvent.BUTTON1) { if (e.isControlDown()) { this.boardView diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuImporter.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuImporter.java index 68bf1e795..5084279c3 100644 --- a/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuImporter.java +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuImporter.java @@ -110,16 +110,6 @@ public void initializeBoard(Node node) throws InvalidFileFormatException { } } } - // - // for(int y = 0; y < size; y++) - // { - // for(int x = 0; x < size; x++) - // { - // SudokuCell cell = sudokuBoard.getCell(x, y); - // System.err.println("(" + x + ", " + y + ") - " + - // cell.getGroupIndex()); - // } - // } puzzle.setCurrentBoard(sudokuBoard); } catch (NumberFormatException e) { diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuView.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuView.java index aa58f9a23..474feb342 100644 --- a/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuView.java +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuView.java @@ -22,10 +22,15 @@ public SudokuView(SudokuBoard board) { for (int k = 0; k < gridSize.width; k++) { Point location = new Point( - k * elementSize.width + (k / minorSize) * 4 + 5, + k * elementSize.width + (k / minorSize) * 4 + 5, // i * elementSize.height + (i / minorSize) * 4 + 5); + // Point location = + // new Point( + // k * elementSize.width, + // i * elementSize.height); SudokuElementView element = new SudokuElementView(board.getCell(k, i)); element.setIndex(i * gridSize.width + k); + element.setIndex(i * gridSize.width); element.setSize(elementSize); element.setLocation(location); elementViews.add(element); diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/elements/NumberTile.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/elements/NumberTile.java index 12183d70d..a94d10e64 100644 --- a/src/main/java/edu/rpi/legup/puzzle/sudoku/elements/NumberTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/elements/NumberTile.java @@ -3,24 +3,11 @@ import edu.rpi.legup.model.elements.PlaceableElement; public class NumberTile extends PlaceableElement { - private int object_num; - public NumberTile() { - super("SUDO-PLAC-0001", "Number Tile", "A numbered tile", null); - object_num = 0; - } - - /** - * @return this object's tile number... - */ - public int getTileNumber() { - return object_num; - } - - /** - * @param num Amount to set tile object to. - */ - public void setTileNumber(int num) { - object_num = num; + super( + "SUDO-ELEM-0001", + "Number Tile", + "A number tile", + "edu/rpi/legup/images/sudoku/tiles/NumberTile.png"); } } diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/elements/UnknownTile.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/elements/UnknownTile.java new file mode 100644 index 000000000..eb9a2c103 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/elements/UnknownTile.java @@ -0,0 +1,13 @@ +package edu.rpi.legup.puzzle.sudoku.elements; + +import edu.rpi.legup.model.elements.PlaceableElement; + +public class UnknownTile extends PlaceableElement { + public UnknownTile() { + super( + "SUDO-ELEM-0002", + "Unknown Tile", + "A blank tile", + "edu/rpi/legup/images/sudoku/tiles/UnknownTile.png"); + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/elements/sudoku_elements_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/sudoku/elements/sudoku_elements_reference_sheet.txt new file mode 100644 index 000000000..b8df27eb6 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/elements/sudoku_elements_reference_sheet.txt @@ -0,0 +1,2 @@ +SUDO-ELEM-0001 : NumberTile +SUDO-ELEM-0002 : UnknownTile \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/AdvancedDeductionDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/AdvancedDeductionDirectRule.java deleted file mode 100644 index 190679b41..000000000 --- a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/AdvancedDeductionDirectRule.java +++ /dev/null @@ -1,99 +0,0 @@ -package edu.rpi.legup.puzzle.sudoku.rules; - -import edu.rpi.legup.model.gameboard.Board; -import edu.rpi.legup.model.gameboard.PuzzleElement; -import edu.rpi.legup.model.rules.DirectRule; -import edu.rpi.legup.model.tree.TreeNode; -import edu.rpi.legup.model.tree.TreeTransition; -import edu.rpi.legup.puzzle.sudoku.SudokuBoard; -import edu.rpi.legup.puzzle.sudoku.SudokuCell; - -public class AdvancedDeductionDirectRule extends DirectRule { - - public AdvancedDeductionDirectRule() { - super( - "SUDO-BASC-0001", - "Advanced Deduction", - "Use of group logic deduces more answers by means of forced by Location and forced" - + " by Deduction", - "edu/rpi/legup/images/sudoku/AdvancedDeduction.png"); - } - - /** - * Checks whether the child node logically follows from the parent node at the specific - * puzzleElement index using this rule - * - * @param transition transition to check - * @param puzzleElement equivalent puzzleElement - * @return null if the child node logically follow from the parent node at the specified - * puzzleElement, otherwise error message - */ - public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) { - SudokuBoard initialBoard = (SudokuBoard) transition.getParents().get(0).getBoard(); - SudokuBoard finalBoard = (SudokuBoard) transition.getBoard(); - - SudokuCell cell = (SudokuCell) finalBoard.getPuzzleElement(puzzleElement); - int index = cell.getIndex(); - int groupSize = initialBoard.getWidth(); - int groupDim = (int) Math.sqrt(groupSize); - int rowIndex = index / groupSize; - int colIndex = index % groupSize; - int relX = rowIndex / groupDim; - int relY = colIndex % groupDim; - int groupNum = rowIndex / groupDim * groupDim + colIndex / groupDim; - boolean[][] possible = new boolean[groupDim][groupDim]; - for (int y = 0; y < groupDim; y++) { - for (int x = 0; x < groupDim; x++) { - SudokuCell c = initialBoard.getCell(groupNum, x, y); - if (c.getData() == cell.getData() && x != relX && y != relY) { - return super.getRuleName() + ": Duplicate value in sub-region"; - } - possible[y][x] = c.getData() == 0; - } - } - for (int y = 0; y < groupDim; y++) { - for (int x = 0; x < groupSize; x++) { - SudokuCell r = initialBoard.getCell(x, (groupNum / groupDim) * groupDim + y); - SudokuCell c = initialBoard.getCell((groupNum % groupDim) * groupDim + y, x); - if (r.getData() == cell.getData()) { - for (int i = 0; i < groupDim; i++) { - possible[y][i] = false; - } - } - if (c.getData() == cell.getData()) { - for (int i = 0; i < groupDim; i++) { - possible[i][y] = false; - } - } - } - } - boolean isForced = false; - for (int y = 0; y < groupDim; y++) { - for (int x = 0; x < groupDim; x++) { - if (possible[y][x] && !isForced) { - isForced = true; - } else { - if (possible[y][x]) { - return super.getInvalidUseOfRuleMessage() + ": Not forced"; - } - } - } - } - if (!isForced) { - return super.getInvalidUseOfRuleMessage() + ": Not forced"; - } - return null; - } - - /** - * Creates a transition {@link Board} that has this rule applied to it using the {@link - * TreeNode}. - * - * @param node tree node used to create default transition board - * @return default board or null if this rule cannot be applied to this tree node - */ - @Override - public Board getDefaultBoard(TreeNode node) { - return null; - } -} diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/LastCellForNumberDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/LastCellForNumberDirectRule.java index fd03ef36c..6544bf7c3 100644 --- a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/LastCellForNumberDirectRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/LastCellForNumberDirectRule.java @@ -15,7 +15,7 @@ public LastCellForNumberDirectRule() { "SUDO-BASC-0002", "Last Cell for Number", "This is the only cell open in its group for some number.", - "edu/rpi/legup/images/sudoku/forcedByElimination.png"); + "edu/rpi/legup/images/sudoku/rules/forcedByElimination.png"); } /** @@ -32,52 +32,146 @@ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElem SudokuBoard finalBoard = (SudokuBoard) transition.getBoard(); SudokuCell cell = (SudokuCell) finalBoard.getPuzzleElement(puzzleElement); + + // Check if empty cell placed if (cell.getData() == 0) { return super.getInvalidUseOfRuleMessage() + ": Cell is not forced at this index"; } - int size = initialBoard.getSize(); - + // Get defaults Set region = initialBoard.getRegion(cell.getGroupIndex()); Set row = initialBoard.getRow(cell.getLocation().y); Set col = initialBoard.getCol(cell.getLocation().x); - boolean contains = false; - if (region.size() == size - 1) { - for (SudokuCell c : region) { - if (cell.getData() == c.getData()) { + // Check if new cell conflicts group + for (SudokuCell c : region) { + if (c.getData() == cell.getData()) { + return super.getInvalidUseOfRuleMessage() + ": Cell is not forced at this index"; + } + } + for (SudokuCell c : row) { + if (c.getData() == cell.getData()) { + return super.getInvalidUseOfRuleMessage() + ": Cell is not forced at this index"; + } + } + for (SudokuCell c : col) { + if (c.getData() == cell.getData()) { + return super.getInvalidUseOfRuleMessage() + ": Cell is not forced at this index"; + } + } + + // // + // Loop to see if the number is constrained to the cell + boolean restrained = true; + for (SudokuCell c : region) { + // Test if its not a valid testing cell + if (c.getData() != 0) { + continue; + } + if (c.getLocation().y == cell.getLocation().y + && c.getLocation().x == cell.getLocation().x) { + continue; + } + // Check if cell is eligible to hold number + Set crow = initialBoard.getRow(c.getLocation().y); + Set ccol = initialBoard.getCol(c.getLocation().x); + boolean contains = false; + for (SudokuCell rc : crow) { + if (rc.getData() == cell.getData()) { contains = true; - break; } } + for (SudokuCell cc : ccol) { + if (cc.getData() == cell.getData()) { + contains = true; + } + } + // Stop if another cell can hold number if (!contains) { - return null; + restrained = false; + break; } } - if (row.size() == size - 1) { - contains = false; - for (SudokuCell c : row) { - if (cell.getData() == c.getData()) { + // Output if success + if (restrained) { + return null; + } + + // // + // Loop to see if the number is constrained to the cell + restrained = true; + for (SudokuCell c : row) { + // Test if its not a valid testing cell + if (c.getData() != 0) { + continue; + } + if (c.getLocation().y == cell.getLocation().y + && c.getLocation().x == cell.getLocation().x) { + continue; + } + // Check if cell is eligible to hold number + Set cregion = initialBoard.getRegion(c.getGroupIndex()); + Set ccol = initialBoard.getCol(c.getLocation().x); + boolean contains = false; + for (SudokuCell rc : cregion) { + if (rc.getData() == cell.getData()) { + contains = true; + } + } + for (SudokuCell cc : ccol) { + if (cc.getData() == cell.getData()) { contains = true; - break; } } + // Stop if another cell can hold number if (!contains) { - return null; + restrained = false; + break; } } - if (col.size() == size - 1) { - contains = false; - for (SudokuCell c : col) { - if (cell.getData() == c.getData()) { + // Output if success + if (restrained) { + return null; + } + + // // + // Loop to see if the number is constrained to the cell + restrained = true; + for (SudokuCell c : col) { + // Test if its not a valid testing cell + if (c.getData() != 0) { + continue; + } + if (c.getLocation().y == cell.getLocation().y + && c.getLocation().x == cell.getLocation().x) { + continue; + } + // Check if cell is eligible to hold number + Set cregion = initialBoard.getRegion(c.getGroupIndex()); + Set crow = initialBoard.getRow(c.getLocation().y); + boolean contains = false; + for (SudokuCell rc : cregion) { + if (rc.getData() == cell.getData()) { contains = true; - break; } } + for (SudokuCell cc : crow) { + if (cc.getData() == cell.getData()) { + contains = true; + } + } + // Stop if another cell can hold number if (!contains) { - return null; + restrained = false; + break; } } + // Output if success + if (restrained) { + return null; + } + + // Output fail return super.getInvalidUseOfRuleMessage() + ": Cell is not forced at this index"; } diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/LastNumberForCellDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/LastNumberForCellDirectRule.java index ca0ac3023..333d91749 100644 --- a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/LastNumberForCellDirectRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/LastNumberForCellDirectRule.java @@ -16,7 +16,7 @@ public LastNumberForCellDirectRule() { "SUDO-BASC-0003", "Last Number for Cell", "This is the only number left that can fit in the cell of a group.", - "edu/rpi/legup/images/sudoku/forcedByDeduction.png"); + "edu/rpi/legup/images/sudoku/rules/forcedByDeduction.png"); } /** @@ -32,28 +32,37 @@ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElem SudokuBoard initialBoard = (SudokuBoard) transition.getParents().get(0).getBoard(); SudokuBoard finalBoard = (SudokuBoard) transition.getBoard(); - int index = puzzleElement.getIndex(); + // Assign basics int groupSize = initialBoard.getWidth(); int groupDim = (int) Math.sqrt(groupSize); + + // Get position info + int index = puzzleElement.getIndex(); int rowIndex = index / groupSize; int colIndex = index % groupSize; - int groupNum = rowIndex / groupDim * groupDim + colIndex % groupDim; + int groupNum = (rowIndex / groupDim) * groupDim + (colIndex / groupDim); + + // Create hashset of all numbers HashSet numbers = new HashSet<>(); for (int i = 1; i <= groupSize; i++) { numbers.add(i); } + + // Run through region, row, col to see contradicitng numbers for (int i = 0; i < groupSize; i++) { SudokuCell cell = initialBoard.getCell(groupNum, i % groupDim, i / groupDim); numbers.remove(cell.getData()); } for (int i = 0; i < groupSize; i++) { - SudokuCell cell = initialBoard.getCell(i, colIndex); + SudokuCell cell = initialBoard.getCell(i, rowIndex); numbers.remove(cell.getData()); } for (int i = 0; i < groupSize; i++) { - SudokuCell cell = initialBoard.getCell(rowIndex, i); + SudokuCell cell = initialBoard.getCell(colIndex, i); numbers.remove(cell.getData()); } + + // Check if plausible if (numbers.size() > 1) { return super.getInvalidUseOfRuleMessage() + ": The number at the index is not forced"; } else { @@ -64,7 +73,11 @@ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElem + ": The number at the index is forced but not correct"; } } - return null; + if (numbers.toArray(new Integer[1])[0] == puzzleElement.getData()) { + return null; + } + return super.getInvalidUseOfRuleMessage() + + ": The number at the index is forced but not correct"; } /** diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/NoCellForNumberColumnContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/NoCellForNumberColumnContradictionRule.java new file mode 100644 index 000000000..c8d627634 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/NoCellForNumberColumnContradictionRule.java @@ -0,0 +1,90 @@ +package edu.rpi.legup.puzzle.sudoku.rules; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.gameboard.PuzzleElement; +import edu.rpi.legup.model.rules.ContradictionRule; +import edu.rpi.legup.puzzle.sudoku.SudokuBoard; +import edu.rpi.legup.puzzle.sudoku.SudokuCell; +import java.util.HashSet; +import java.util.Set; + +public class NoCellForNumberColumnContradictionRule extends ContradictionRule { + + public NoCellForNumberColumnContradictionRule() { + super( + "SUDO-CONT-0003", + "No Cell for Number (Column)", + "Process of elimination yields no valid numbers for an empty cell in a column.", + "edu/rpi/legup/images/sudoku/rules/NoCellForNumberColumn.png"); + } + + /** + * Checks whether the transition has a contradiction at the specific puzzleElement index using + * this rule + * + * @param board board to check contradiction + * @param puzzleElement equivalent puzzleElement + * @return null if the transition contains a contradiction at the specified puzzleElement, + * otherwise error message + */ + @Override + public String checkContradictionAt(Board board, PuzzleElement puzzleElement) { + SudokuBoard sudokuBoard = (SudokuBoard) board; + SudokuCell cell = (SudokuCell) sudokuBoard.getPuzzleElement(puzzleElement); + if (cell.getData() != 0) { + return super.getNoContradictionMessage(); + } + + int groupSize = sudokuBoard.getSize(); + + Set col = sudokuBoard.getCol(cell.getGroupIndex()); + Set numbersNotInColumn = new HashSet<>(); + + for (int i = 1; i <= groupSize; i++) { + numbersNotInColumn.add(i); + } + for (SudokuCell c : col) { + if (c.getData() != 0) { + numbersNotInColumn.remove(c.getData()); + } + } + + for (Integer i : numbersNotInColumn) { + // Check if number can be in cell + boolean canFit = false; + for (SudokuCell c : col) { + if (c.getData() != 0) { + continue; + } + + // Get row and col groups + Set region = sudokuBoard.getRow(c.getLocation().y); + Set row = sudokuBoard.getCol(c.getLocation().x); + + // Check if it alr exists in row or col + boolean duplicate = false; + for (SudokuCell rc : region) { + if (rc.getData() == i) { + duplicate = true; + } + } + for (SudokuCell cc : row) { + if (cc.getData() == i) { + duplicate = true; + } + } + + // If there is no duplicate it can exist in the region + if (!duplicate) { + canFit = true; + break; + } + } + // If the number can't fit anywhere in region then contradiction + if (!canFit) { + return null; + } + } + return super.getNoContradictionMessage(); + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/NoCellForNumberRegionContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/NoCellForNumberRegionContradictionRule.java new file mode 100644 index 000000000..f5106b858 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/NoCellForNumberRegionContradictionRule.java @@ -0,0 +1,90 @@ +package edu.rpi.legup.puzzle.sudoku.rules; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.gameboard.PuzzleElement; +import edu.rpi.legup.model.rules.ContradictionRule; +import edu.rpi.legup.puzzle.sudoku.SudokuBoard; +import edu.rpi.legup.puzzle.sudoku.SudokuCell; +import java.util.HashSet; +import java.util.Set; + +public class NoCellForNumberRegionContradictionRule extends ContradictionRule { + + public NoCellForNumberRegionContradictionRule() { + super( + "SUDO-CONT-0001", + "No Cell for Number (Region)", + "Process of elimination yields no valid numbers for an empty cell in a region.", + "edu/rpi/legup/images/sudoku/rules/NoCellForNumberRegion.png"); + } + + /** + * Checks whether the transition has a contradiction at the specific puzzleElement index using + * this rule + * + * @param board board to check contradiction + * @param puzzleElement equivalent puzzleElement + * @return null if the transition contains a contradiction at the specified puzzleElement, + * otherwise error message + */ + @Override + public String checkContradictionAt(Board board, PuzzleElement puzzleElement) { + SudokuBoard sudokuBoard = (SudokuBoard) board; + SudokuCell cell = (SudokuCell) sudokuBoard.getPuzzleElement(puzzleElement); + if (cell.getData() != 0) { + return super.getNoContradictionMessage(); + } + + int groupSize = sudokuBoard.getSize(); + + Set region = sudokuBoard.getRegion(cell.getGroupIndex()); + Set numbersNotInRegion = new HashSet<>(); + + for (int i = 1; i <= groupSize; i++) { + numbersNotInRegion.add(i); + } + for (SudokuCell c : region) { + if (c.getData() != 0) { + numbersNotInRegion.remove(c.getData()); + } + } + + for (Integer i : numbersNotInRegion) { + // Check if number can be in cell + boolean canFit = false; + for (SudokuCell c : region) { + if (c.getData() != 0) { + continue; + } + + // Get row and col groups + Set row = sudokuBoard.getRow(c.getLocation().y); + Set col = sudokuBoard.getCol(c.getLocation().x); + + // Check if it alr exists in row or col + boolean duplicate = false; + for (SudokuCell rc : row) { + if (rc.getData() == i) { + duplicate = true; + } + } + for (SudokuCell cc : col) { + if (cc.getData() == i) { + duplicate = true; + } + } + + // If there is no duplicate it can exist in the region + if (!duplicate) { + canFit = true; + break; + } + } + // If the number can't fit anywhere in region then contradiction + if (!canFit) { + return null; + } + } + return super.getNoContradictionMessage(); + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/NoCellForNumberRowContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/NoCellForNumberRowContradictionRule.java new file mode 100644 index 000000000..e3f9f764a --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/NoCellForNumberRowContradictionRule.java @@ -0,0 +1,90 @@ +package edu.rpi.legup.puzzle.sudoku.rules; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.gameboard.PuzzleElement; +import edu.rpi.legup.model.rules.ContradictionRule; +import edu.rpi.legup.puzzle.sudoku.SudokuBoard; +import edu.rpi.legup.puzzle.sudoku.SudokuCell; +import java.util.HashSet; +import java.util.Set; + +public class NoCellForNumberRowContradictionRule extends ContradictionRule { + + public NoCellForNumberRowContradictionRule() { + super( + "SUDO-CONT-0002", + "No Cell for Number (Row)", + "Process of elimination yields no valid numbers for an empty cell in a row.", + "edu/rpi/legup/images/sudoku/rules/NoCellForNumberRow.png"); + } + + /** + * Checks whether the transition has a contradiction at the specific puzzleElement index using + * this rule + * + * @param board board to check contradiction + * @param puzzleElement equivalent puzzleElement + * @return null if the transition contains a contradiction at the specified puzzleElement, + * otherwise error message + */ + @Override + public String checkContradictionAt(Board board, PuzzleElement puzzleElement) { + SudokuBoard sudokuBoard = (SudokuBoard) board; + SudokuCell cell = (SudokuCell) sudokuBoard.getPuzzleElement(puzzleElement); + if (cell.getData() != 0) { + return super.getNoContradictionMessage(); + } + + int groupSize = sudokuBoard.getSize(); + + Set row = sudokuBoard.getRow(cell.getGroupIndex()); + Set numbersNotInRow = new HashSet<>(); + + for (int i = 1; i <= groupSize; i++) { + numbersNotInRow.add(i); + } + for (SudokuCell c : row) { + if (c.getData() != 0) { + numbersNotInRow.remove(c.getData()); + } + } + + for (Integer i : numbersNotInRow) { + // Check if number can be in cell + boolean canFit = false; + for (SudokuCell c : row) { + if (c.getData() != 0) { + continue; + } + + // Get row and col groups + Set region = sudokuBoard.getRow(c.getLocation().y); + Set col = sudokuBoard.getCol(c.getLocation().x); + + // Check if it alr exists in row or col + boolean duplicate = false; + for (SudokuCell rc : region) { + if (rc.getData() == i) { + duplicate = true; + } + } + for (SudokuCell cc : col) { + if (cc.getData() == i) { + duplicate = true; + } + } + + // If there is no duplicate it can exist in the region + if (!duplicate) { + canFit = true; + break; + } + } + // If the number can't fit anywhere in region then contradiction + if (!canFit) { + return null; + } + } + return super.getNoContradictionMessage(); + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/NoSolutionContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/NoNumberForCellContradictionRule.java similarity index 72% rename from src/main/java/edu/rpi/legup/puzzle/sudoku/rules/NoSolutionContradictionRule.java rename to src/main/java/edu/rpi/legup/puzzle/sudoku/rules/NoNumberForCellContradictionRule.java index e44728d3e..6ea8f0a2a 100644 --- a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/NoSolutionContradictionRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/NoNumberForCellContradictionRule.java @@ -8,14 +8,14 @@ import java.util.HashSet; import java.util.Set; -public class NoSolutionContradictionRule extends ContradictionRule { +public class NoNumberForCellContradictionRule extends ContradictionRule { - public NoSolutionContradictionRule() { + public NoNumberForCellContradictionRule() { super( - "SUDO-CONT-0001", - "No Solution for Cell", + "SUDO-CONT-0004", + "No Number for Cell", "Process of elimination yields no valid numbers for an empty cell.", - "edu/rpi/legup/images/sudoku/NoSolution.png"); + "edu/rpi/legup/images/sudoku/rules/NoSolution.png"); } /** @@ -41,21 +41,19 @@ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) { Set row = sudokuBoard.getRow(cell.getLocation().y); Set col = sudokuBoard.getCol(cell.getLocation().x); Set solution = new HashSet<>(); - for (int i = 1; i <= groupSize; i++) { - solution.add(i); + for (SudokuCell s : region) { + solution.add(s.getData()); } - - for (SudokuCell c : region) { - solution.remove(c.getData()); - } - for (SudokuCell c : row) { - solution.remove(c.getData()); + for (SudokuCell s : row) { + solution.add(s.getData()); } - for (SudokuCell c : col) { - solution.remove(c.getData()); + + for (SudokuCell s : col) { + solution.add(s.getData()); } + solution.remove(0); - if (solution.isEmpty()) { + if (solution.size() == 9) { return null; } diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleNumberCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleCellsForNumberColumnCaseRule.java similarity index 53% rename from src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleNumberCaseRule.java rename to src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleCellsForNumberColumnCaseRule.java index e6ab0e64c..bab0bc79b 100644 --- a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleNumberCaseRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleCellsForNumberColumnCaseRule.java @@ -6,21 +6,26 @@ import edu.rpi.legup.model.rules.CaseRule; import edu.rpi.legup.model.tree.TreeTransition; import edu.rpi.legup.puzzle.sudoku.GroupType; -import edu.rpi.legup.puzzle.sudoku.PossibleNumberCaseBoard; +import edu.rpi.legup.puzzle.sudoku.ModelSudokuBoard; import edu.rpi.legup.puzzle.sudoku.SudokuBoard; import edu.rpi.legup.puzzle.sudoku.SudokuCell; import java.util.ArrayList; -import java.util.List; import java.util.Set; -public class PossibleNumberCaseRule extends CaseRule { +public class PossibleCellsForNumberColumnCaseRule extends CaseRule { - public PossibleNumberCaseRule() { + // Board math for translating indexes to numbers + private ModelSudokuBoard model = new ModelSudokuBoard(); + + // Old board for caseBoard reference + private SudokuBoard lagBoard; + + public PossibleCellsForNumberColumnCaseRule() { super( - "SUDO-CASE-0002", - "Possible Numbers for Cell", + "SUDO-CASE-0004", + "Possible Cells for Number - Column", "An empty cell has a limited set of possible numbers that can fill it.", - "edu/rpi/legup/images/sudoku/PossibleValues.png"); + "edu/rpi/legup/images/sudoku/rules/possible_cells_number_column.png"); } /** @@ -50,12 +55,12 @@ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElem @Override public CaseBoard getCaseBoard(Board board) { - SudokuBoard sudokuBoard = (SudokuBoard) board; - PossibleNumberCaseBoard caseBoard = new PossibleNumberCaseBoard(sudokuBoard, this, null); - for (int i = 0; i < sudokuBoard.getSize(); i++) { - caseBoard.addPickableRegion(i); - caseBoard.addPickableRow(i); - caseBoard.addPickableCol(i); + SudokuBoard sudokuBoard = (SudokuBoard) board.copy(); + lagBoard = (SudokuBoard) sudokuBoard.copy(); + CaseBoard caseBoard = new CaseBoard(sudokuBoard, this); + for (PuzzleElement puzzleElement : sudokuBoard.getPuzzleElements()) { + puzzleElement.setData(model.getModelColumnNumbers(puzzleElement.getIndex())); + caseBoard.addPickableElement(puzzleElement); } return caseBoard; } @@ -69,7 +74,7 @@ public CaseBoard getCaseBoard(Board board) { */ @Override public ArrayList getCases(Board board, PuzzleElement puzzleElement) { - return getCases(board, puzzleElement, 1, GroupType.REGION); + return getCases(board, puzzleElement, 1, GroupType.COLUMN); } /** @@ -84,48 +89,19 @@ public ArrayList getCases(Board board, PuzzleElement puzzleElement) { public ArrayList getCases( Board board, PuzzleElement puzzleElement, int value, GroupType groupType) { ArrayList cases = new ArrayList<>(); - SudokuBoard sudokuBoard = (SudokuBoard) board; - List caseCells = new ArrayList<>(); - SudokuCell cell = (SudokuCell) puzzleElement; + SudokuBoard sudokuBoard = lagBoard; + SudokuCell sourceCell = (SudokuCell) puzzleElement; - Set group; - if (groupType == GroupType.REGION) { - group = sudokuBoard.getRegion(cell.getGroupIndex()); - } else { - if (groupType == GroupType.ROW) { - group = sudokuBoard.getRow(cell.getLocation().y); - } else { - group = sudokuBoard.getCol(cell.getLocation().x); + Set group = sudokuBoard.getCol(sourceCell.getLocation().x); + for (SudokuCell cell : group) { + if (cell.getData() == 0) { + Board newCase = sudokuBoard.copy(); + PuzzleElement element = newCase.getPuzzleElement(cell); + element.setData(model.getModelColumnNumbers(sourceCell.getIndex())); + newCase.addModifiedData(element); + cases.add(newCase); } } - - for (SudokuCell c : group) { - if (c.getData() == 0) { - Set blockableCells = sudokuBoard.getRegion(c.getGroupIndex()); - blockableCells.addAll(sudokuBoard.getRow(c.getLocation().y)); - blockableCells.addAll(sudokuBoard.getCol(c.getLocation().x)); - - boolean repeat = false; - for (SudokuCell bc : blockableCells) { - if (bc.getData() == value) { - repeat = true; - break; - } - } - if (!repeat) { - caseCells.add(c); - } - } - } - - for (SudokuCell c : caseCells) { - Board newCase = sudokuBoard.copy(); - PuzzleElement element = newCase.getPuzzleElement(c); - element.setData(value); - newCase.addModifiedData(element); - cases.add(newCase); - } - return cases; } } diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleCellsForNumberRegionCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleCellsForNumberRegionCaseRule.java new file mode 100644 index 000000000..47e408369 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleCellsForNumberRegionCaseRule.java @@ -0,0 +1,104 @@ +package edu.rpi.legup.puzzle.sudoku.rules; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.gameboard.CaseBoard; +import edu.rpi.legup.model.gameboard.PuzzleElement; +import edu.rpi.legup.model.rules.CaseRule; +import edu.rpi.legup.model.tree.TreeTransition; +import edu.rpi.legup.puzzle.sudoku.*; +import java.util.ArrayList; +import java.util.Set; + +public class PossibleCellsForNumberRegionCaseRule extends CaseRule { + + // Board math for translating indexes to numbers + private ModelSudokuBoard model = new ModelSudokuBoard(); + + // Old board for caseBoard reference + private SudokuBoard lagBoard; + + public PossibleCellsForNumberRegionCaseRule() { + super( + "SUDO-CASE-0002", + "Possible Cells for Number - Region", + "An empty cell has a limited set of possible numbers that can fill it.", + "edu/rpi/legup/images/sudoku/rules/possible_cells_number_region.png"); + } + + /** + * Checks whether the transition logically follows from the parent node using this rule + * + * @param transition transition to check + * @return null if the child node logically follow from the parent node, otherwise error message + */ + @Override + public String checkRuleRaw(TreeTransition transition) { + return null; + } + + /** + * Checks whether the child node logically follows from the parent node at the specific + * puzzleElement index using this rule + * + * @param transition transition to check + * @param puzzleElement equivalent puzzleElement + * @return null if the child node logically follow from the parent node at the specified + * puzzleElement, otherwise error message + */ + @Override + public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) { + return null; + } + + @Override + public CaseBoard getCaseBoard(Board board) { + SudokuBoard sudokuBoard = (SudokuBoard) board.copy(); + lagBoard = (SudokuBoard) sudokuBoard.copy(); + CaseBoard caseBoard = new CaseBoard(sudokuBoard, this); + for (PuzzleElement puzzleElement : sudokuBoard.getPuzzleElements()) { + puzzleElement.setData(model.getModelRegionNumbers(puzzleElement.getIndex())); + caseBoard.addPickableElement(puzzleElement); + } + return caseBoard; + } + + /** + * Gets the possible cases at a specific location based on this case rule + * + * @param board the current board state + * @param puzzleElement equivalent puzzleElement + * @return a list of elements the specified could be + */ + @Override + public ArrayList getCases(Board board, PuzzleElement puzzleElement) { + return getCases(board, puzzleElement, 1, GroupType.REGION); + } + + /** + * Gets the possible cases at a specific location based on this case rule + * + * @param board the current board state + * @param puzzleElement equivalent puzzleElement + * @param value value that the rule will be applied from + * @param groupType group type + * @return a list of elements the specified could be + */ + public ArrayList getCases( + Board board, PuzzleElement puzzleElement, int value, GroupType groupType) { + ArrayList cases = new ArrayList<>(); + SudokuBoard sudokuBoard = lagBoard; + SudokuCell sourceCell = (SudokuCell) puzzleElement; + + Set group = sudokuBoard.getRegion(sourceCell.getGroupIndex()); + for (SudokuCell cell : group) { + if (cell.getData() == 0) { + Board newCase = sudokuBoard.copy(); + PuzzleElement element = newCase.getPuzzleElement(cell); + element.setData(model.getModelRegionNumbers(sourceCell.getIndex())); + newCase.addModifiedData(element); + cases.add(newCase); + } + } + return cases; + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleCellsForNumberRowCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleCellsForNumberRowCaseRule.java new file mode 100644 index 000000000..868541377 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleCellsForNumberRowCaseRule.java @@ -0,0 +1,107 @@ +package edu.rpi.legup.puzzle.sudoku.rules; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.gameboard.CaseBoard; +import edu.rpi.legup.model.gameboard.PuzzleElement; +import edu.rpi.legup.model.rules.CaseRule; +import edu.rpi.legup.model.tree.TreeTransition; +import edu.rpi.legup.puzzle.sudoku.GroupType; +import edu.rpi.legup.puzzle.sudoku.ModelSudokuBoard; +import edu.rpi.legup.puzzle.sudoku.SudokuBoard; +import edu.rpi.legup.puzzle.sudoku.SudokuCell; +import java.util.ArrayList; +import java.util.Set; + +public class PossibleCellsForNumberRowCaseRule extends CaseRule { + + // Board math for translating indexes to numbers + private ModelSudokuBoard model = new ModelSudokuBoard(); + + // Old board for caseBoard reference + private SudokuBoard lagBoard; + + public PossibleCellsForNumberRowCaseRule() { + super( + "SUDO-CASE-0003", + "Possible Cells for Number - Row", + "An empty cell has a limited set of possible numbers that can fill it.", + "edu/rpi/legup/images/sudoku/rules/possible_cells_number_row.png"); + } + + /** + * Checks whether the transition logically follows from the parent node using this rule + * + * @param transition transition to check + * @return null if the child node logically follow from the parent node, otherwise error message + */ + @Override + public String checkRuleRaw(TreeTransition transition) { + return null; + } + + /** + * Checks whether the child node logically follows from the parent node at the specific + * puzzleElement index using this rule + * + * @param transition transition to check + * @param puzzleElement equivalent puzzleElement + * @return null if the child node logically follow from the parent node at the specified + * puzzleElement, otherwise error message + */ + @Override + public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) { + return null; + } + + @Override + public CaseBoard getCaseBoard(Board board) { + SudokuBoard sudokuBoard = (SudokuBoard) board.copy(); + lagBoard = (SudokuBoard) sudokuBoard.copy(); + CaseBoard caseBoard = new CaseBoard(sudokuBoard, this); + for (PuzzleElement puzzleElement : sudokuBoard.getPuzzleElements()) { + puzzleElement.setData(model.getModelRowNumbers(puzzleElement.getIndex())); + caseBoard.addPickableElement(puzzleElement); + } + return caseBoard; + } + + /** + * Gets the possible cases at a specific location based on this case rule + * + * @param board the current board state + * @param puzzleElement equivalent puzzleElement + * @return a list of elements the specified could be + */ + @Override + public ArrayList getCases(Board board, PuzzleElement puzzleElement) { + return getCases(board, puzzleElement, 1, GroupType.ROW); + } + + /** + * Gets the possible cases at a specific location based on this case rule + * + * @param board the current board state + * @param puzzleElement equivalent puzzleElement + * @param value value that the rule will be applied from + * @param groupType group type + * @return a list of elements the specified could be + */ + public ArrayList getCases( + Board board, PuzzleElement puzzleElement, int value, GroupType groupType) { + ArrayList cases = new ArrayList<>(); + SudokuBoard sudokuBoard = lagBoard; + SudokuCell sourceCell = (SudokuCell) puzzleElement; + + Set group = sudokuBoard.getRow(sourceCell.getLocation().y); + for (SudokuCell cell : group) { + if (cell.getData() == 0) { + Board newCase = sudokuBoard.copy(); + PuzzleElement element = newCase.getPuzzleElement(cell); + element.setData(model.getModelRowNumbers(sourceCell.getIndex())); + newCase.addModifiedData(element); + cases.add(newCase); + } + } + return cases; + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleCellCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleNumbersForCellCaseRule.java similarity index 59% rename from src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleCellCaseRule.java rename to src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleNumbersForCellCaseRule.java index fb6da62d4..e17acc26b 100644 --- a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleCellCaseRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/PossibleNumbersForCellCaseRule.java @@ -5,19 +5,18 @@ import edu.rpi.legup.model.gameboard.PuzzleElement; import edu.rpi.legup.model.rules.CaseRule; import edu.rpi.legup.model.tree.TreeTransition; -import edu.rpi.legup.puzzle.sudoku.SudokuBoard; -import edu.rpi.legup.puzzle.sudoku.SudokuCell; +import edu.rpi.legup.puzzle.sudoku.*; import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; +import java.util.List; -public class PossibleCellCaseRule extends CaseRule { - public PossibleCellCaseRule() { +public class PossibleNumbersForCellCaseRule extends CaseRule { + + public PossibleNumbersForCellCaseRule() { super( "SUDO-CASE-0001", - "Possible Cells for Number", - "A number has a limited set of cells in which it can be placed.", - "edu/rpi/legup/images/sudoku/possible_cells_number.png"); + "Possible Numbers for Cell", + "An empty cell has a limited set of possible numbers that can fill it.", + "edu/rpi/legup/images/sudoku/rules/PossibleValues.png"); } /** @@ -66,42 +65,34 @@ public CaseBoard getCaseBoard(Board board) { */ @Override public ArrayList getCases(Board board, PuzzleElement puzzleElement) { - ArrayList cases = new ArrayList<>(); - SudokuBoard sudokuBoard = (SudokuBoard) board; - SudokuCell cell = (SudokuCell) puzzleElement; - - Set possibleValue = new HashSet<>(); - for (int i = 1; i <= sudokuBoard.getSize(); i++) { - possibleValue.add(i); - } - - int groupNum = cell.getGroupIndex(); - for (SudokuCell c : sudokuBoard.getRegion(groupNum)) { - if (c.getData().equals(c.getData())) { - possibleValue.remove(c.getData()); - } - } - - int rowNum = cell.getLocation().y; - for (SudokuCell c : sudokuBoard.getRegion(rowNum)) { - if (c.getData().equals(c.getData())) { - possibleValue.remove(c.getData()); - } - } + return getCases(board, puzzleElement, 1, GroupType.REGION); + } - int colNum = cell.getLocation().x; - for (SudokuCell c : sudokuBoard.getRegion(colNum)) { - if (c.getData().equals(c.getData())) { - possibleValue.remove(c.getData()); - } + /** + * Gets the possible cases at a specific location based on this case rule + * + * @param board the current board state + * @param puzzleElement equivalent puzzleElement + * @param value value that the rule will be applied from + * @param groupType group type + * @return a list of elements the specified could be + */ + public ArrayList getCases( + Board board, PuzzleElement puzzleElement, int value, GroupType groupType) { + ArrayList cases = new ArrayList<>(); + if (puzzleElement == null) { + return cases; } - for (Integer i : possibleValue) { - SudokuBoard newCase = sudokuBoard.copy(); + SudokuBoard sudokuBoard = (SudokuBoard) board; + List caseCells = new ArrayList<>(); + SudokuCell cell = (SudokuCell) puzzleElement; - PuzzleElement newCasePuzzleElement = newCase.getPuzzleElement(puzzleElement); - newCasePuzzleElement.setData(i); - newCase.addModifiedData(newCasePuzzleElement); + for (int i = 1; i <= 9; i++) { + Board newCase = sudokuBoard.copy(); + PuzzleElement element = newCase.getPuzzleElement(puzzleElement); + element.setData(i); + newCase.addModifiedData(element); cases.add(newCase); } diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/RepeatedNumberContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/RepeatedNumberContradictionRule.java index 955414e8e..f8172d071 100644 --- a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/RepeatedNumberContradictionRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/RepeatedNumberContradictionRule.java @@ -12,10 +12,10 @@ public class RepeatedNumberContradictionRule extends ContradictionRule { public RepeatedNumberContradictionRule() { super( - "SUDO-CONT-0002", + "SUDO-CONT-0005", "Repeated Numbers", "Two identical numbers are placed in the same group.", - "edu/rpi/legup/images/sudoku/RepeatedNumber.png"); + "edu/rpi/legup/images/sudoku/rules/RepeatedNumber.png"); } /** @@ -29,39 +29,51 @@ public RepeatedNumberContradictionRule() { */ @Override public String checkContradictionAt(Board board, PuzzleElement puzzleElement) { + // Get board to check SudokuBoard sudokuBoard = (SudokuBoard) board; - SudokuCell cell = (SudokuCell) sudokuBoard.getPuzzleElement(puzzleElement); - if (cell.getData() == 0) { - return super.getNoContradictionMessage(); - } - Set region = sudokuBoard.getRegion(cell.getGroupIndex()); - Set row = sudokuBoard.getRow(cell.getLocation().y); - Set col = sudokuBoard.getCol(cell.getLocation().x); + // Loop all group indexes + for (int i = 0; i < 9; i++) { + // Get regions and sets to check duplicates + Set region = sudokuBoard.getRegion(i); + Set regionDup = new HashSet<>(); + + Set row = sudokuBoard.getRow(i); + Set rowDup = new HashSet<>(); - Set regionDup = new HashSet<>(); - Set rowDup = new HashSet<>(); - Set colDup = new HashSet<>(); + Set col = sudokuBoard.getCol(i); + Set colDup = new HashSet<>(); - for (SudokuCell c : region) { - if (regionDup.contains(c.getData())) { - return null; + // Check for non zero duplicates to trigger contradiction + for (SudokuCell c : region) { + if (c.getData() == 0) { + continue; + } + if (regionDup.contains(c.getData())) { + return null; + } + regionDup.add(c.getData()); } - regionDup.add(c.getData()); - } - for (SudokuCell c : row) { - if (rowDup.contains(c.getData())) { - return null; + for (SudokuCell c : row) { + if (c.getData() == 0) { + continue; + } + if (rowDup.contains(c.getData())) { + return null; + } + rowDup.add(c.getData()); } - rowDup.add(c.getData()); - } - for (SudokuCell c : col) { - if (colDup.contains(c.getData())) { - return null; + for (SudokuCell c : col) { + if (c.getData() == 0) { + continue; + } + if (colDup.contains(c.getData())) { + return null; + } + colDup.add(c.getData()); } - colDup.add(c.getData()); } return super.getNoContradictionMessage(); diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/sudoku_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/sudoku_reference_sheet.txt index a8635330d..ceffa168c 100644 --- a/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/sudoku_reference_sheet.txt +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/rules/sudoku_reference_sheet.txt @@ -1,9 +1,13 @@ -SUDO-BASC-0001 : AdvancedDeductionDirectRule SUDO-BASC-0002 : LastCellForNumberDirectRule SUDO-BASC-0003 : LastNumberForCellDirectRule -SUDO-CONT-0001 : NoSolutionContradictionRule -SUDO-CONT-0002 : RepeatedNumberContradictionRule +SUDO-CONT-0001 : NoCellForNumberRegionContradictionRule +SUDO-CONT-0002 : NoCellForNumberRowContradictionRule +SUDO-CONT-0003 : NoCellForNumberColumnContradictionRule +SUDO-CONT-0004 : NoNumberForCellContradictionRule +SUDO-CONT-0005 : RepeatedNumberContradictionRule -SUDO-CASE-0001 : PossibleCellCaseRule -SUDO-CASE-0002 : PossibleNumberCaseRule \ No newline at end of file +SUDO-CASE-0001 : PossibleNumbersForCellCaseRule +SUDO-CASE-0002 : PossibleCellsForNumberRegionCaseRule +SUDO-CASE-0003 : PossibleCellsForNumberRowCaseRule +SUDO-CASE-0004 : PossibleCellsForNumberColumnCaseRule \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTent.java b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTent.java index 68c97865d..35993303a 100644 --- a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTent.java +++ b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTent.java @@ -3,6 +3,7 @@ import edu.rpi.legup.model.Puzzle; import edu.rpi.legup.model.gameboard.Board; import edu.rpi.legup.model.gameboard.PuzzleElement; +import edu.rpi.legup.model.rules.ContradictionRule; import java.util.List; public class TreeTent extends Puzzle { @@ -58,7 +59,20 @@ public boolean isValidDimensions(int rows, int columns) { */ @Override public boolean isBoardComplete(Board board) { - return false; + TreeTentBoard treeTentBoard = (TreeTentBoard) board; + + for (ContradictionRule rule : contradictionRules) { + if (rule.checkContradiction(treeTentBoard) == null) { + return false; + } + } + for (PuzzleElement data : treeTentBoard.getPuzzleElements()) { + TreeTentCell cell = (TreeTentCell) data; + if (cell.getType() == TreeTentType.UNKNOWN) { + return false; + } + } + return true; } /** diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/elements/GrassTile.java b/src/main/java/edu/rpi/legup/puzzle/treetent/elements/GrassTile.java index 5356120a8..1d33b9035 100644 --- a/src/main/java/edu/rpi/legup/puzzle/treetent/elements/GrassTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/treetent/elements/GrassTile.java @@ -6,7 +6,7 @@ public class GrassTile extends PlaceableElement { public GrassTile() { super( - "TREE-PlAC-0002", + "TREE-ELEM-0001", "Grass Tile", "The grass crest tile", "edu/rpi/legup/images/treetent/grass.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/elements/TentTile.java b/src/main/java/edu/rpi/legup/puzzle/treetent/elements/TentTile.java index 950aebfa7..96124a98d 100644 --- a/src/main/java/edu/rpi/legup/puzzle/treetent/elements/TentTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/treetent/elements/TentTile.java @@ -6,7 +6,7 @@ public class TentTile extends PlaceableElement { public TentTile() { super( - "TREE-PLAC-0001", + "TREE-ELEM-0002", "Tent Tile", "The tent tile", "edu/rpi/legup/images/treetent/tent.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/elements/TreeTile.java b/src/main/java/edu/rpi/legup/puzzle/treetent/elements/TreeTile.java index d04886ed5..3d94cbfba 100644 --- a/src/main/java/edu/rpi/legup/puzzle/treetent/elements/TreeTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/treetent/elements/TreeTile.java @@ -1,12 +1,12 @@ package edu.rpi.legup.puzzle.treetent.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class TreeTile extends NonPlaceableElement { +public class TreeTile extends PlaceableElement { public TreeTile() { super( - "TREE-UNPL-0001", + "TREE-ELEM-0003", "Tree Tile", "The tree tile", "edu/rpi/legup/images/treetent/tree.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/elements/UnknownTile.java b/src/main/java/edu/rpi/legup/puzzle/treetent/elements/UnknownTile.java index a54240efd..99b75b60c 100644 --- a/src/main/java/edu/rpi/legup/puzzle/treetent/elements/UnknownTile.java +++ b/src/main/java/edu/rpi/legup/puzzle/treetent/elements/UnknownTile.java @@ -1,11 +1,11 @@ package edu.rpi.legup.puzzle.treetent.elements; -import edu.rpi.legup.model.elements.NonPlaceableElement; +import edu.rpi.legup.model.elements.PlaceableElement; -public class UnknownTile extends NonPlaceableElement { +public class UnknownTile extends PlaceableElement { public UnknownTile() { super( - "TREE-UNPL-0002", + "TREE-ELEM-0004", "Unknown Tile", "The blank tile", "edu/rpi/legup/images/treetent/UnknownTile.png"); diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/elements/treetent_elements_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/treetent/elements/treetent_elements_reference_sheet.txt new file mode 100644 index 000000000..e0cfc1dfa --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/treetent/elements/treetent_elements_reference_sheet.txt @@ -0,0 +1,4 @@ +TREE-ELEM-0001 : GrassTile +TREE-ELEM-0002 : TentTile +TREE-ELEM-0003 : TreeTile +TREE-ELEM-0004 : UnknownTile \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/rules/FillinRowCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/treetent/rules/FillinRowCaseRule.java index aaa1a8fbc..8fe9b6873 100644 --- a/src/main/java/edu/rpi/legup/puzzle/treetent/rules/FillinRowCaseRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/treetent/rules/FillinRowCaseRule.java @@ -61,7 +61,11 @@ public CaseBoard getCaseBoard(Board board) { */ @Override public ArrayList getCases(Board board, PuzzleElement puzzleElement) { + if (puzzleElement == null) { + return new ArrayList(); + } ArrayList cases; + List group; int tentsLeft; TreeTentClue clue = ((TreeTentClue) puzzleElement); diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/rules/LinkTentCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/treetent/rules/LinkTentCaseRule.java index bd303174a..cbe91c3a7 100644 --- a/src/main/java/edu/rpi/legup/puzzle/treetent/rules/LinkTentCaseRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/treetent/rules/LinkTentCaseRule.java @@ -60,6 +60,10 @@ public CaseBoard getCaseBoard(Board board) { @Override public ArrayList getCases(Board board, PuzzleElement puzzleElement) { ArrayList cases = new ArrayList(); + if (puzzleElement == null) { + return cases; + } + TreeTentCell cell = (TreeTentCell) puzzleElement; List adj = ((TreeTentBoard) board).getAdjacent(cell, TreeTentType.TREE); List lines = ((TreeTentBoard) board).getLines(); diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/rules/LinkTreeCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/treetent/rules/LinkTreeCaseRule.java index 03d039898..153692ad0 100644 --- a/src/main/java/edu/rpi/legup/puzzle/treetent/rules/LinkTreeCaseRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/treetent/rules/LinkTreeCaseRule.java @@ -62,6 +62,10 @@ public CaseBoard getCaseBoard(Board board) { @Override public ArrayList getCases(Board board, PuzzleElement puzzleElement) { ArrayList cases = new ArrayList<>(); + if (puzzleElement == null) { + return cases; + } + TreeTentBoard treeTentBoard = (TreeTentBoard) board; TreeTentCell cell = (TreeTentCell) puzzleElement; List adjCells = treeTentBoard.getAdjacent(cell, TreeTentType.TENT); diff --git a/src/main/java/edu/rpi/legup/ui/CreatePuzzleDialog.java b/src/main/java/edu/rpi/legup/ui/CreatePuzzleDialog.java index 70fbea033..bde2df3d5 100644 --- a/src/main/java/edu/rpi/legup/ui/CreatePuzzleDialog.java +++ b/src/main/java/edu/rpi/legup/ui/CreatePuzzleDialog.java @@ -10,6 +10,10 @@ import java.util.Objects; import javax.swing.*; +/** + * Provides the user interface components for creating a new puzzle in the Legup application. This + * package includes classes for displaying dialog boxes to configure and initialize puzzles. + */ public class CreatePuzzleDialog extends JDialog { private HomePanel homePanel; @@ -17,6 +21,14 @@ public class CreatePuzzleDialog extends JDialog { private JComboBox gameBox; private ActionListener gameBoxListener = new ActionListener() { + /** + * An ActionListener that handles changes in the drop-down menu for selecting puzzle + * types. When a new item is selected in the drop-down menu, this listener updates + * the visibility of the text input area and the row/column input fields based on + * the selected puzzle type. If "ShortTruthTable" is selected, the text input area + * is shown and the row/column fields are hidden. For other puzzle types, the + * row/column fields are shown and the text input area is hidden. + */ @Override public void actionPerformed(ActionEvent e) { JComboBox comboBox = (JComboBox) e.getSource(); @@ -57,9 +69,7 @@ public void actionPerformed(ActionEvent e) { */ @Override public void actionPerformed(ActionEvent ae) { - String game = - Config.convertDisplayNameToClassName( - (String) gameBox.getSelectedItem()); + String game = getGame(); // Check if all 3 TextFields are filled if (game.equals("ShortTruthTable") && textArea.getText().isEmpty()) { @@ -67,9 +77,7 @@ public void actionPerformed(ActionEvent ae) { return; } if (!game.equals("ShortTruthTable") - && (game.isEmpty() - || rows.getText().isEmpty() - || columns.getText().isEmpty())) { + && (game.isEmpty() || getRows().isEmpty() || getColumns().isEmpty())) { System.out.println("Unfilled fields"); return; } @@ -81,8 +89,8 @@ public void actionPerformed(ActionEvent ae) { } else { homePanel.openEditorWithNewPuzzle( game, - Integer.valueOf(rows.getText()), - Integer.valueOf(columns.getText())); + Integer.valueOf(getRows()), + Integer.valueOf(getColumns())); } setVisible(false); } catch (IllegalArgumentException e) { @@ -106,6 +114,12 @@ public void actionPerformed(ActionEvent e) { } }; + /** + * Constructs a new CreatePuzzleDialog + * + * @param parent the parent frame of the dialog + * @param homePanel the home panel where the created puzzle will be added + */ public CreatePuzzleDialog(JFrame parent, HomePanel homePanel) { super(parent, true); @@ -184,6 +198,10 @@ public CreatePuzzleDialog(JFrame parent, HomePanel homePanel) { cancel.addActionListener(cursorPressedCancel); } + /** + * Initializes the puzzle options available for selection in the dialog. The options are + * retrieved from the game board facade and sorted alphabetically. + */ public void initPuzzles() { this.games = GameBoardFacade.getInstance() @@ -194,7 +212,12 @@ public void initPuzzles() { gameBox = new JComboBox(this.games); } - // ^This method seems useless and never got covered + /** + * Handles the action events for the dialog, including interactions with the Ok and Cancel + * buttons + * + * @param e The action event to be processed + */ public void actionPerformed(ActionEvent e) { if (e.getSource() == ok) { String game = Config.convertDisplayNameToClassName((String) gameBox.getSelectedItem()); @@ -211,8 +234,7 @@ public void actionPerformed(ActionEvent e) { } this.setVisible(false); } catch (IllegalArgumentException exception) { - // Don't do anything. This is here to prevent the dialog from closing if the - // dimensions are + // Do nothing. This is here to prevent the dialog from closing if the dimensions are // invalid. } } else { @@ -223,4 +245,40 @@ public void actionPerformed(ActionEvent e) { } } } + + /** + * Retrieves the selected game from the combo box + * + * @return the class name of the selected game + */ + public String getGame() { + return Config.convertDisplayNameToClassName((String) gameBox.getSelectedItem()); + } + + /** + * Retrieves the number of rows specified in the dialog + * + * @return the number of rows as a string + */ + public String getRows() { + return rows.getText(); + } + + /** + * Retrieves the number of columns specified in the dialog + * + * @return the number of columns as a string + */ + public String getColumns() { + return columns.getText(); + } + + /** + * Retrieves the text entered in the text area, split by new lines. + * + * @return an array of strings, each representing as a line of text + */ + public String[] getTextArea() { + return textArea.getText().split("\n"); + } } diff --git a/src/main/java/edu/rpi/legup/ui/DynamicView.java b/src/main/java/edu/rpi/legup/ui/DynamicView.java index 8d3024c86..fa0004d0b 100644 --- a/src/main/java/edu/rpi/legup/ui/DynamicView.java +++ b/src/main/java/edu/rpi/legup/ui/DynamicView.java @@ -16,6 +16,10 @@ import javax.swing.*; import javax.swing.event.ChangeEvent; +/** + * A JPanel that provides a dynamic view with zooming capabilities for different types of content. + * This class supports views such as game boards or proof trees, allowing users to zoom in and out. + */ public class DynamicView extends JPanel { private ScrollView scrollView; @@ -29,6 +33,12 @@ public class DynamicView extends JPanel { private static final Font INFO_FONT = MaterialFonts.REGULAR; private static final Color INFO_COLOR = MaterialColors.GRAY_900; + /** + * Constructs a new DynamicView with the specified ScrollView and view type + * + * @param scrollView the ScrollView that provides the content to be displayed and zoomed + * @param type the type of dynamic view to set up (e.g., BOARD or PROOF_TREE) + */ public DynamicView(ScrollView scrollView, DynamicViewType type) { this.scrollView = scrollView; @@ -185,34 +195,61 @@ public void componentResized(ComponentEvent e) { return zoomWrapper; } + /** + * Gets the ScrollView component associated with this DynamicView + * + * @return the ScrollView component + */ public ScrollView getScrollView() { return this.scrollView; } + /** + * Gets the zoom wrapper that contains the zooming controls + * + * @return the zoom wrapper with zooming controls + */ public JPanel getZoomWrapper() { return this.zoomWrapper; } + /** + * Gets the zoomer that contains the zoomer component + * + * @return the zoomer with the zoomer component + */ public JPanel getZoomer() { return this.zoomer; } + /** + * Updates the status label with an informational message + * + * @param message the informational message to display + */ public void updateInfo(String message) { status.setFont(INFO_FONT); status.setForeground(INFO_COLOR); status.setText(message); } + /** + * Updates the status label with an error message + * + * @param message the error message to display + */ public void updateError(String message) { status.setFont(ERROR_FONT); status.setForeground(ERROR_COLOR); status.setText(message); } + /** Clears the status label */ public void resetStatus() { status.setText(""); } + /** Resets the view to its default state and zooms the content to fit the screen */ public void reset() { Puzzle puzzle = GameBoardFacade.getInstance().getPuzzleModule(); Board board1 = GameBoardFacade.getInstance().getBoard(); @@ -221,6 +258,7 @@ public void reset() { this.getScrollView().zoomFit(); } + /** Fits the board view to the screen */ protected void fitBoardViewToScreen() { scrollView.zoomFit(); } diff --git a/src/main/java/edu/rpi/legup/ui/DynamicViewType.java b/src/main/java/edu/rpi/legup/ui/DynamicViewType.java index 8c2f285cd..c4bb940e0 100644 --- a/src/main/java/edu/rpi/legup/ui/DynamicViewType.java +++ b/src/main/java/edu/rpi/legup/ui/DynamicViewType.java @@ -1,5 +1,14 @@ package edu.rpi.legup.ui; +/** + * An enumeration representing the different types of dynamic views supported by the application. + * The two types of views are: + * + *

    + *
  • {@code BOARD} - Represents a dynamic view of a game board + *
  • {@code PROOF_TREE} - Represents a dynamic view of a proof tree + *
+ */ public enum DynamicViewType { BOARD, PROOF_TREE diff --git a/src/main/java/edu/rpi/legup/ui/HomePanel.java b/src/main/java/edu/rpi/legup/ui/HomePanel.java index 11f51eb0e..75761c475 100644 --- a/src/main/java/edu/rpi/legup/ui/HomePanel.java +++ b/src/main/java/edu/rpi/legup/ui/HomePanel.java @@ -25,6 +25,11 @@ import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; +/** + * The {@code HomePanel} class represents the home panel of the LEGUP application. This panel + * provides buttons for functionalities of opening the proof editor, opening the puzzle editor, and + * performing batch grading. It also includes a menu bar with options for preferences. + */ public class HomePanel extends LegupPanel { private static final Logger LOGGER = LogManager.getLogger(HomePanel.class.getName()); private LegupUI legupUI; @@ -36,44 +41,35 @@ public class HomePanel extends LegupPanel { private final int buttonSize = 100; + /** Initialize the proof solver to an empty panel with no puzzle */ private ActionListener openProofListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - Object[] items = legupUI.getProofEditor().promptPuzzle(); - if (items == null) { - // The attempt to prompt a puzzle ended gracefully (cancel) - return; - } - String fileName = (String) items[0]; - File puzzleFile = (File) items[1]; - legupUI.getProofEditor().loadPuzzle(fileName, puzzleFile); + legupUI.getProofEditor().loadPuzzle("", null); } }; - private ActionListener openPuzzleListener = - new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - Object[] items = legupUI.getPuzzleEditor().promptPuzzle(); - if (items == null) { - // The attempt to prompt a puzzle ended gracefully (cancel) - return; - } - String fileName = (String) items[0]; - File puzzleFile = (File) items[1]; - legupUI.getPuzzleEditor().loadPuzzle(fileName, puzzleFile); - } - }; - - public HomePanel(FileDialog fileDialog, JFrame frame, LegupUI legupUI) { + /** + * Constructs a {@code HomePanel} with the specified {@code JFrame} and {@code LegupUI}. + * + * @param frame the main application frame + * @param legupUI the LEGUP user interface + */ + public HomePanel(JFrame frame, LegupUI legupUI) { this.legupUI = legupUI; this.frame = frame; setLayout(new GridLayout(1, 2)); + setPreferredSize(new Dimension(440, 250)); initText(); initButtons(); } + /** + * Creates and returns the menu bar for this panel + * + * @return the menu bar + */ public JMenuBar getMenuBar() { this.menuBar = new JMenuBar(); JMenu settings = new JMenu("Settings"); @@ -102,23 +98,33 @@ public JMenuBar getMenuBar() { return this.menuBar; } + /** Makes the panel visible and sets the menu bar of the frame */ @Override public void makeVisible() { render(); frame.setJMenuBar(this.getMenuBar()); } + /** + * Resizes the provided icon to the specified width and height + * + * @param icon the icon to resize + * @param width the target width + * @param height the target height + * @return the resized icon + */ private static ImageIcon resizeButtonIcon(ImageIcon icon, int width, int height) { Image image = icon.getImage(); Image resizedImage = image.getScaledInstance(width, height, Image.SCALE_SMOOTH); return new ImageIcon(resizedImage); } + /** Initializes the buttons for this panel */ private void initButtons() { - this.buttons = new JButton[4]; + this.buttons = new JButton[3]; this.buttons[0] = - new JButton("Solve Puzzle") { + new JButton("Puzzle Solver") { { setSize(buttonSize, buttonSize); setMaximumSize(getSize()); @@ -136,7 +142,7 @@ private void initButtons() { this.buttons[0].addActionListener(CursorController.createListener(this, openProofListener)); this.buttons[1] = - new JButton("Create Puzzle") { + new JButton("Puzzle Editor") { { setSize(buttonSize, buttonSize); setMaximumSize(getSize()); @@ -150,35 +156,17 @@ private void initButtons() { this.buttons[1].setIcon(resizeButtonIcon(button1Icon, this.buttonSize, this.buttonSize)); this.buttons[1].setHorizontalTextPosition(AbstractButton.CENTER); this.buttons[1].setVerticalTextPosition(AbstractButton.BOTTOM); - this.buttons[1].addActionListener(l -> this.openNewPuzzleDialog()); - - this.buttons[2] = - new JButton("Edit Puzzle") { - { - setSize(buttonSize, buttonSize); - setMaximumSize(getSize()); - } - }; - URL button2IconLocation = - ClassLoader.getSystemClassLoader() - .getResource("edu/rpi/legup/images/Legup/homepanel/puzzle_file.png"); - ImageIcon button2Icon = new ImageIcon(button2IconLocation); - this.buttons[2].setFocusPainted(false); - this.buttons[2].setIcon(resizeButtonIcon(button2Icon, this.buttonSize, this.buttonSize)); - this.buttons[2].setHorizontalTextPosition(AbstractButton.CENTER); - this.buttons[2].setVerticalTextPosition(AbstractButton.BOTTOM); - this.buttons[2].addActionListener( - CursorController.createListener(this, openPuzzleListener)); // PLACEHOLDER + this.buttons[1].addActionListener(l -> this.openPuzzleEditorDialog()); for (int i = 0; i < this.buttons.length - 1; i++) { // -1 to avoid the batch grader button this.buttons[i].setBounds(200, 200, 700, 700); } - this.buttons[3] = new JButton("Batch Grader"); - this.buttons[3].setFocusPainted(false); - this.buttons[3].setHorizontalTextPosition(AbstractButton.CENTER); - this.buttons[3].setVerticalTextPosition(AbstractButton.BOTTOM); + this.buttons[2] = new JButton("Batch Grader"); + this.buttons[2].setFocusPainted(false); + this.buttons[2].setHorizontalTextPosition(AbstractButton.CENTER); + this.buttons[2].setVerticalTextPosition(AbstractButton.BOTTOM); - this.buttons[3].addActionListener( + this.buttons[2].addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -192,6 +180,10 @@ public void actionPerformed(ActionEvent e) { }); } + /** + * Opens a folder chooser dialog and grades puzzles in the selected folder. The results are + * written to a CSV file. + */ public void checkFolder() { GameBoardFacade facade = GameBoardFacade.getInstance(); /* @@ -272,13 +264,17 @@ public void checkFolder() { } } catch (IOException ex) { LOGGER.error(ex.getMessage()); - this.buttons[3].addActionListener((ActionEvent e) -> use_xml_to_check()); + this.buttons[2].addActionListener((ActionEvent e) -> use_xml_to_check()); } } /** - * @effect batch grade using .xml parser - go through a collection of files and report their - * "solved?" status + * Processes XML files within a selected directory and generates a CSV report on their "solved?" + * status. The method allows the user to select a directory, and evaluates each XML file for a + * "solved?" status. Results are saved in a "result.csv" file. + * + * @effect Selects a directory, processes each XML file to check for "solved?" status, and + * writes results to "result.csv". Opens the CSV file upon completion. */ private void use_xml_to_check() { /* Select a folder, go through each .xml file in the subfolders, look for "isSolved" flag */ @@ -477,6 +473,10 @@ public void endDocument() throws SAXException { } } + /** + * Initializes the text labels for the user interface. Sets up labels for welcome message, led + * by Bram, and version information. + */ private void initText() { // TODO: add version text after auto-changing version label is implemented. (text[2] = // version) @@ -498,11 +498,12 @@ private void initText() { this.text[1] = credits; } + /** Renders the user interface components */ private void render() { this.removeAll(); this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); - this.legupUI.setTitle("LEGUP: A Better Way to Learn Formal Logic"); + this.legupUI.setTitle("LEGUP: A Better Way To Learn Formal Logic"); JPanel buttons = new JPanel(); buttons.add(Box.createRigidArea(new Dimension(5, 0))); @@ -510,11 +511,8 @@ private void render() { buttons.add(Box.createRigidArea(new Dimension(5, 0))); buttons.add(this.buttons[1]); buttons.add(Box.createRigidArea(new Dimension(5, 0))); - buttons.add(this.buttons[2]); - buttons.add(Box.createRigidArea(new Dimension(5, 0))); - JPanel batchGraderButton = new JPanel(); - batchGraderButton.add(this.buttons[3]); + batchGraderButton.add(this.buttons[2]); batchGraderButton.setAlignmentX(Component.CENTER_ALIGNMENT); this.add(Box.createRigidArea(new Dimension(0, 5))); @@ -526,11 +524,29 @@ private void render() { this.add(Box.createRigidArea(new Dimension(0, 5))); } - private void openNewPuzzleDialog() { - CreatePuzzleDialog cpd = new CreatePuzzleDialog(this.frame, this); - cpd.setVisible(true); + /** + * Opens the puzzle editor dialog with no selected puzzle, leaving a blank panel + * + * @throws IllegalArgumentException if the configuration parameters are invalid (should never + * happen) + */ + private void openPuzzleEditorDialog() { + String game = ""; + int r = 0; + int c = 0; + + try { + this.openEditorWithNewPuzzle(game, r, c); + } catch (IllegalArgumentException e) { + System.out.println("Failed to open editor with new puzzle"); + e.printStackTrace(System.out); + } } + /** + * Opens a dialog to select a directory, recursively processes the directory to grade puzzles, + * and generates a CSV report of the grading results. + */ private void checkProofAll() { /* * Select dir to grade; recursively grade sub-dirs using traverseDir() @@ -574,6 +590,14 @@ private void checkProofAll() { JOptionPane.showMessageDialog(null, "Batch grading complete."); } + /** + * Recursively traverses directories to grade puzzles and writes results to a CSV file + * + * @param folder the folder to traverse + * @param writer the BufferedWriter to write results to the CSV file + * @param path the current path within the directory structure + * @throws IOException if an I/O error occurs while writing to the CSV file + */ private void traverseDir(File folder, BufferedWriter writer, String path) throws IOException { // Recursively traverse directory GameBoardFacade facade = GameBoardFacade.getInstance(); @@ -626,28 +650,46 @@ private void traverseDir(File folder, BufferedWriter writer, String path) throws } } + /** + * Opens the puzzle editor for the specified puzzle with the specified dimensions + * + * @param game the name of the game + * @param rows the number of rows in the puzzle + * @param columns the number of columns in the puzzle + * @throws IllegalArgumentException if the dimensions are invalid + */ public void openEditorWithNewPuzzle(String game, int rows, int columns) throws IllegalArgumentException { - // Validate the dimensions - GameBoardFacade facade = GameBoardFacade.getInstance(); - boolean isValidDimensions = facade.validateDimensions(game, rows, columns); - if (!isValidDimensions) { - JOptionPane.showMessageDialog( - null, - "The dimensions you entered are invalid. Please double check \n" - + "the number of rows and columns and try again.", - "ERROR: Invalid Dimensions", - JOptionPane.ERROR_MESSAGE); - throw new IllegalArgumentException("ERROR: Invalid dimensions given"); - } + if (game.isEmpty()) { + this.legupUI.displayPanel(2); + this.legupUI.getPuzzleEditor().loadPuzzleFromHome(game, rows, columns); + } else { + // Validate the dimensions + GameBoardFacade facade = GameBoardFacade.getInstance(); + boolean isValidDimensions = facade.validateDimensions(game, rows, columns); + if (!isValidDimensions) { + JOptionPane.showMessageDialog( + null, + "The dimensions you entered are invalid. Please double check \n" + + "the number of rows and columns and try again.", + "ERROR: Invalid Dimensions", + JOptionPane.ERROR_MESSAGE); + throw new IllegalArgumentException("ERROR: Invalid dimensions given"); + } - // Set game type on the puzzle editor - this.legupUI.displayPanel(2); - this.legupUI.getPuzzleEditor().loadPuzzleFromHome(game, rows, columns); + if (this.legupUI == null) { + System.err.println("Error: legupUI is null in HomePanel"); + return; + } + + // Set game type on the puzzle editor + this.legupUI.displayPanel(2); + this.legupUI.getPuzzleEditor().loadPuzzleFromHome(game, rows, columns); + } } /** - * Opens the puzzle editor for the specified game with the given statements + * Opens the puzzle editor for the specified puzzle with the given statements * * @param game a String containing the name of the game * @param statements an array of statements diff --git a/src/main/java/edu/rpi/legup/ui/LegupPanel.java b/src/main/java/edu/rpi/legup/ui/LegupPanel.java index d16167b3d..639f21211 100644 --- a/src/main/java/edu/rpi/legup/ui/LegupPanel.java +++ b/src/main/java/edu/rpi/legup/ui/LegupPanel.java @@ -2,9 +2,15 @@ import javax.swing.*; +/** + * An abstract base class for panels in the LEGUP application. This class extends {@link JPanel} and + * defines common properties and methods for all panels in the LEGUP user interface (currently only + * implements toolbar scale) + */ public abstract class LegupPanel extends JPanel { /** Alerts panel that it will be going visible now */ protected final int TOOLBAR_ICON_SCALE = 40; + /** Abstract method to make the panel visible */ public abstract void makeVisible(); } diff --git a/src/main/java/edu/rpi/legup/ui/LegupUI.java b/src/main/java/edu/rpi/legup/ui/LegupUI.java index eb8b1663c..75f822a6c 100644 --- a/src/main/java/edu/rpi/legup/ui/LegupUI.java +++ b/src/main/java/edu/rpi/legup/ui/LegupUI.java @@ -14,6 +14,11 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +/** + * The main user interface class for the LEGUP application. This class extends {@link JFrame} and + * implements {@link WindowListener} to manage the overall window and provide functionality for + * displaying various panels. + */ public class LegupUI extends JFrame implements WindowListener { private static final Logger LOGGER = LogManager.getLogger(LegupUI.class.getName()); @@ -36,7 +41,7 @@ public static String getOS() { return os; } - /** LegupUI Constructor - creates a new LegupUI to setup the menu and toolbar */ + /** LegupUI Constructor - creates a new LegupUI to set up the menu and toolbar */ public LegupUI() { setTitle("LEGUP"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); @@ -91,17 +96,24 @@ public void keyTyped(KeyEvent e) { setVisible(true); } + /** Initializes the panels used in the UI. Sets up the layout and adds panels to the window. */ private void initPanels() { window = new JPanel(); window.setLayout(new BorderLayout()); add(window); panels = new LegupPanel[3]; - panels[0] = new HomePanel(this.fileDialog, this, this); + panels[0] = new HomePanel(this, this); panels[1] = new ProofEditorPanel(this.fileDialog, this, this); panels[2] = new PuzzleEditorPanel(this.fileDialog, this, this); } + /** + * Displays the specified panel + * + * @param option the index of the panel to display + * @throws InvalidParameterException if the option is out of range + */ protected void displayPanel(int option) { if (option > panels.length || option < 0) { throw new InvalidParameterException("Invalid option"); @@ -115,29 +127,29 @@ protected void displayPanel(int option) { repaint(); } + /** + * Gets the ProofEditorPanel instance + * + * @return the ProofEditorPanel + */ public ProofEditorPanel getProofEditor() { return (ProofEditorPanel) panels[1]; } + /** + * Gets the PuzzleEditorPanel instance + * + * @return the PuzzleEditorPanel + */ public PuzzleEditorPanel getPuzzleEditor() { return (PuzzleEditorPanel) panels[2]; } + /** Repaints the tree view in the proof editor. */ public void repaintTree() { getProofEditor().repaintTree(); } - private void directions() { - JOptionPane.showMessageDialog( - null, - "For every move you make, you must provide a rules for it (located in the Rules" - + " panel).\n" - + "While working on the edu.rpi.legup.puzzle, you may click on the \"Check\"" - + " button to test your proof for correctness.", - "Directions", - JOptionPane.PLAIN_MESSAGE); - } - public void showStatus(String status, boolean error) { showStatus(status, error, 1); } @@ -150,8 +162,13 @@ public void showStatus(String status, boolean error, int timer) { // TODO: implement } - // ask to edu.rpi.legup.save current proof - public boolean noquit(String instr) { + /** + * Prompts the user to confirm if they want to exit LEGUP + * + * @param instr the prompt message + * @return true if the user chooses not to quit, false otherwise + */ + public boolean exit(String instr) { int n = JOptionPane.showConfirmDialog(null, instr, "Confirm", JOptionPane.YES_NO_OPTION); return n != JOptionPane.YES_OPTION; } @@ -161,7 +178,7 @@ public void windowOpened(WindowEvent e) {} public void windowClosing(WindowEvent e) { if (GameBoardFacade.getInstance().getHistory().getIndex() > -1) { - if (noquit("Exiting LEGUP?")) { + if (exit("Exiting LEGUP?")) { this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); } else { this.setDefaultCloseOperation(EXIT_ON_CLOSE); @@ -183,22 +200,47 @@ public void windowActivated(WindowEvent e) {} public void windowDeactivated(WindowEvent e) {} + /** + * Gets the BoardView instance from the proof editor + * + * @return the BoardView + */ public BoardView getBoardView() { return getProofEditor().getBoardView(); } + /** + * Gets the BoardView instance from the puzzle editor + * + * @return the BoardView + */ public BoardView getEditorBoardView() { return getPuzzleEditor().getBoardView(); } + /** + * Gets the DynamicView instance from the proof editor + * + * @return the DynamicView + */ public DynamicView getDynamicBoardView() { return getProofEditor().getDynamicBoardView(); } + /** + * Gets the DynamicView instance from the puzzle editor. + * + * @return the DynamicView + */ public DynamicView getEditorDynamicBoardView() { return getPuzzleEditor().getDynamicBoardView(); } + /** + * Gets the TreePanel instance from the proof editor + * + * @return the TreePanel + */ public TreePanel getTreePanel() { return getProofEditor().getTreePanel(); } diff --git a/src/main/java/edu/rpi/legup/ui/PickGameDialog.java b/src/main/java/edu/rpi/legup/ui/PickGameDialog.java index f703ffcbc..3b66931a7 100644 --- a/src/main/java/edu/rpi/legup/ui/PickGameDialog.java +++ b/src/main/java/edu/rpi/legup/ui/PickGameDialog.java @@ -15,6 +15,10 @@ import javax.swing.JLabel; import javax.swing.JTextField; +/** + * A dialog for selecting a game. This class extends {@link JDialog} and implements {@link + * ActionListener} to handle user interactions for selecting a game type and puzzle file. + */ public class PickGameDialog extends JDialog implements ActionListener { JLabel gameLabel = new JLabel("Game:"); String[] games; @@ -106,6 +110,10 @@ public PickGameDialog(JFrame parent, boolean pickBothAtOnce) { c.add(autojustifyCheckBox); } + /** + * Initializes the available games and puzzles. Populates the {@link JComboBox} with game types + * and sets up the puzzle options. + */ public void initPuzzles() { Object[] o = GameBoardFacade.getInstance().getConfig().getPuzzleClassNames().toArray(); @@ -128,14 +136,30 @@ public void initPuzzles() { gameBox = new JComboBox(games); } + /** + * Gets the selected puzzle file path + * + * @return the puzzle file path as a String + */ public String getPuzzle() { return puzzleBox.getText(); } + /** + * Returns the selected puzzle + * + * @return the selected puzzle as a String + */ public String getGame() { return (String) gameBox.getSelectedItem(); } + /** + * Handles action events for the dialog components. Responds to user interactions such as + * selecting a puzzle, choosing a puzzle file, and pressing the 'Ok' or 'Cancel' buttons. + * + * @param e the action event + */ public void actionPerformed(ActionEvent e) { if (e.getSource() == gameBox) { int index = gameBox.getSelectedIndex(); diff --git a/src/main/java/edu/rpi/legup/ui/PreferencesDialog.java b/src/main/java/edu/rpi/legup/ui/PreferencesDialog.java index 475f4bb68..e90d06640 100644 --- a/src/main/java/edu/rpi/legup/ui/PreferencesDialog.java +++ b/src/main/java/edu/rpi/legup/ui/PreferencesDialog.java @@ -17,6 +17,12 @@ import javax.imageio.ImageIO; import javax.swing.*; +/** + * A dialog for managing user preferences in the LEGUP application. This dialog allows users to + * configure various settings such as screen mode, update preferences, work directory path, and + * specific features related to board and tree views. Users can access this dialog from the home + * screen or proof editor. + */ public class PreferencesDialog extends JDialog { private RuleFrame rulesFrame; @@ -47,12 +53,24 @@ public class PreferencesDialog extends JDialog { } } + /** + * Creates a new instance of PreferencesDialog for the proof editor + * + * @param frame the parent frame + * @param rules the RuleFrame associated with the proof editor + * @return a new instance of PreferencesDialog + */ public static PreferencesDialog CreateDialogForProofEditor(Frame frame, RuleFrame rules) { PreferencesDialog p = new PreferencesDialog(frame); p.rulesFrame = rules; return p; } + /** + * Constructs a PreferencesDialog + * + * @param frame the parent frame + */ public PreferencesDialog(Frame frame) { super(frame); @@ -102,6 +120,11 @@ public PreferencesDialog(Frame frame) { setVisible(true); } + /** + * Toggles between dark mode and light mode based on the given preferences + * + * @param prefs the LegupPreferences instance holding user preferences + */ private void toggleDarkMode(LegupPreferences prefs) { try { if (Boolean.valueOf(prefs.getUserPref(LegupPreferences.DARK_MODE))) { @@ -115,6 +138,11 @@ private void toggleDarkMode(LegupPreferences prefs) { } } + /** + * Creates the general preferences tab + * + * @return a JScrollPane containing the general preferences panel + */ private JScrollPane createGeneralTab() { LegupPreferences prefs = LegupPreferences.getInstance(); JScrollPane scrollPane = new JScrollPane(); @@ -335,6 +363,14 @@ private JScrollPane createPuzzleTab(Puzzle puzzle) { return scrollPane; } + /** + * Creates a JPanel that represents a single row for a rule in the rule list. Each row displays + * the rule's name and an area for showing keyboard shortcuts associated with the rule. The + * keyboard shortcuts are dynamically updated based on user input. + * + * @param rule the rule object to be displayed + * @return a JPanel representing the row for the rule + */ private JPanel createRuleRow(Rule rule) { JPanel ruleRow = new JPanel(); ruleRow.setLayout(new BorderLayout()); @@ -387,6 +423,13 @@ public void keyPressed(KeyEvent e) { return ruleRow; } + /** + * Creates a JPanel containing a left-aligned label with the specified text. This label is + * typically used for section headings or descriptive text in the preferences dialog. + * + * @param text the text to be displayed on the label + * @return a JPanel containing the left-aligned label + */ private JPanel createLeftLabel(String text) { JPanel labelRow = new JPanel(); labelRow.setLayout(new BorderLayout()); @@ -400,12 +443,24 @@ private JPanel createLeftLabel(String text) { return labelRow; } + /** + * Creates a JSeparator with a maximum height of 5 pixels. This separator is used to visually + * divide sections in the preferences dialog. + * + * @return a JSeparator with a fixed height + */ private JSeparator createLineSeparator() { JSeparator separator = new JSeparator(); separator.setMaximumSize(new Dimension(Integer.MAX_VALUE, 5)); return separator; } + /** + * Applies the current user preferences and updates the associated components. This method + * retrieves user preferences from the dialog's components and stores them in the {@link + * LegupPreferences} instance. It also updates the rule panels in the rules frame if it is not + * null. + */ public void applyPreferences() { LegupPreferences prefs = LegupPreferences.getInstance(); prefs.setUserPref(LegupPreferences.WORK_DIRECTORY, workDirectory.getText()); diff --git a/src/main/java/edu/rpi/legup/ui/ProofEditorPanel.java b/src/main/java/edu/rpi/legup/ui/ProofEditorPanel.java index 645a2c0d7..5ecbd5564 100644 --- a/src/main/java/edu/rpi/legup/ui/ProofEditorPanel.java +++ b/src/main/java/edu/rpi/legup/ui/ProofEditorPanel.java @@ -36,6 +36,13 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +/** + * {@code ProofEditorPanel} is a panel that serves as the main user interface component for the + * proof editing functionality of LEGUP. It provides the graphical components and interactive + * elements necessary for editing and managing proofs, including toolbars, menus, and views for + * different aspects of proof editing. It also manages interactions with the rest of the application + * and updates the UI based on user actions and application state changes. + */ public class ProofEditorPanel extends LegupPanel implements IHistoryListener { private static final Logger LOGGER = LogManager.getLogger(ProofEditorPanel.class.getName()); private JMenuBar mBar; @@ -46,8 +53,8 @@ public class ProofEditorPanel extends LegupPanel implements IHistoryListener { private DynamicView dynamicBoardView; private JSplitPane topHalfPanel, mainPanel; private TitledBorder boardBorder; - - private JButton[] toolBarButtons; + private JButton[] toolBar1Buttons; + private JButton[] toolBar2Buttons; private JMenu file; private JMenuItem newPuzzle, resetPuzzle, @@ -67,7 +74,8 @@ public class ProofEditorPanel extends LegupPanel implements IHistoryListener { private JMenu about, help; private JMenuItem helpLegup, aboutLegup; - private JToolBar toolBar; + private JToolBar toolBar1; + private JToolBar toolBar2; private BoardView boardView; private JFileChooser folderBrowser; @@ -81,7 +89,6 @@ public class ProofEditorPanel extends LegupPanel implements IHistoryListener { public static final int IMD_FEEDBACK = 32; public static final int INTERN_RO = 64; public static final int AUTO_JUST = 128; - static final int[] TOOLBAR_SEPARATOR_BEFORE = {2, 4, 8}; private static final String[] PROFILES = { "No Assistance", "Rigorous Proof", @@ -111,6 +118,13 @@ public class ProofEditorPanel extends LegupPanel implements IHistoryListener { protected JMenuItem testAI = new JMenuItem("Test AI!"); protected JMenuItem hintAI = new JMenuItem("Hint"); + /** + * Constructs a new {@code ProofEditorPanel} with the specified parameters + * + * @param fileDialog the {@code FileDialog} used for file operations + * @param frame the {@code JFrame} that contains this panel + * @param legupUI the {@code LegupUI} instance managing the user interface + */ public ProofEditorPanel(FileDialog fileDialog, JFrame frame, LegupUI legupUI) { this.fileDialog = fileDialog; this.frame = frame; @@ -119,22 +133,46 @@ public ProofEditorPanel(FileDialog fileDialog, JFrame frame, LegupUI legupUI) { setPreferredSize(new Dimension(800, 700)); } + /** + * Makes the panel visible by setting up the toolbar and content components. This method also + * sets the menu bar of the frame to the one used by this panel. + */ @Override public void makeVisible() { this.removeAll(); - setupToolBar(); + setupToolBar1(); setupContent(); frame.setJMenuBar(getMenuBar()); } + /** + * Constructs and returns the {@code JMenuBar} for this panel. It populates it with various + * {@code JMenu} and {@code JMenuItem} components related to file operations, editing, viewing, + * and proof management. The menu bar includes: + * + *
    + *
  • {@code File} menu with options to open a new puzzle, reset the puzzle, save the proof, + * access preferences, and exit the editor. + *
  • {@code Edit} menu with options for undo, redo, and fitting the board or tree to the + * screen. + *
  • {@code Proof} menu with options for adding, deleting, merging, collapsing elements, and + * toggling settings related to rule applications and feedback. + *
  • {@code About} menu with options to view information about the application and access + * help resources. + *
+ * + *

Accelerator keys are set based on the operating system (Mac or non-Mac). + * + * @return the {@code JMenuBar} instance containing the menus and menu items for this panel + */ public JMenuBar getMenuBar() { if (mBar != null) return mBar; mBar = new JMenuBar(); file = new JMenu("File"); newPuzzle = new JMenuItem("Open"); - resetPuzzle = new JMenuItem("Reset Puzzle"); + resetPuzzle = new JMenuItem("Reset"); // genPuzzle = new JMenuItem("Puzzle Generators"); // TODO: implement puzzle // generator saveProofAs = new JMenuItem("Save As"); // create a new file to save @@ -310,6 +348,7 @@ public JMenuBar getMenuBar() { } else { resetPuzzle.setAccelerator(KeyStroke.getKeyStroke('R', InputEvent.CTRL_DOWN_MASK)); } + file.addSeparator(); file.add(saveProofAs); @@ -457,6 +496,10 @@ public void actionPerformed(ActionEvent e) { return mBar; } + /** + * Clears the current puzzle, resets the UI to display the initial panel, and nullifies the + * references to the tree panel and board view. + */ public void exitEditor() { // Wipes the puzzle entirely as if LEGUP just started GameBoardFacade.getInstance().clearPuzzle(); @@ -465,7 +508,14 @@ public void exitEditor() { boardView = null; } - // File opener + /** + * Opens a file chooser dialog allowing the user to select a directory. It uses the user's + * preferred directory or the last saved path if available. The selected directory is used to + * set the new working directory. + * + * @return an array containing the file name and the selected file, or {@code null} if the + * operation was canceled + */ public Object[] promptPuzzle() { GameBoardFacade facade = GameBoardFacade.getInstance(); if (facade.getBoard() != null) { @@ -505,9 +555,16 @@ public Object[] promptPuzzle() { return null; } + System.out.println(preferences.getSavedPath()); return new Object[] {fileName, puzzleFile}; } + /** + * Calls {@link #promptPuzzle()} to get the file information and then loads the puzzle using the + * provided file name and file object. Updates the frame title to reflect the puzzle name. If + * the file is not valid or an error occurs, an error message is shown, and the user is prompted + * to try loading another puzzle. + */ public void loadPuzzle() { Object[] items = promptPuzzle(); // Return if items == null (cancel) @@ -519,7 +576,19 @@ public void loadPuzzle() { loadPuzzle(fileName, puzzleFile); } + /** + * Attempts to load a puzzle from the given file. If successful, it updates the UI to display + * the puzzle and changes the frame title to include the puzzle name. If the file is invalid or + * cannot be read, it shows an appropriate error message and prompts the user to try loading + * another puzzle. + * + * @param fileName the name of the file to load + * @param puzzleFile the file object representing the puzzle file + */ public void loadPuzzle(String fileName, File puzzleFile) { + if (puzzleFile == null && fileName.isEmpty()) { + legupUI.displayPanel(1); + } if (puzzleFile != null && puzzleFile.exists()) { try { legupUI.displayPanel(1); @@ -554,7 +623,11 @@ public void loadPuzzle(String fileName, File puzzleFile) { } } - /** save the proof in the current file */ + /** + * Uses the current puzzle and its associated exporter to save the puzzle data to the file + * currently being used. If the puzzle or exporter is null, or if an error occurs during export, + * the method will catch the exception and print the stack trace. + */ private void direct_save() { Puzzle puzzle = GameBoardFacade.getInstance().getPuzzleModule(); if (puzzle == null) { @@ -574,7 +647,11 @@ private void direct_save() { } } - /** Create a new file and save proof to it */ + /** + * Opens a file chooser dialog for the user to select a directory. The chosen directory is used + * to determine where the puzzle file will be saved. If an exporter is available, it will be + * used to export the puzzle data to the selected path. + */ private void saveProofAs() { Puzzle puzzle = GameBoardFacade.getInstance().getPuzzleModule(); if (puzzle == null) { @@ -612,6 +689,11 @@ private void saveProofAs() { } // Hyperlink for help button; links to wiki page for tutorials + /** + * Opens the default web browser to a help page related to the type of puzzle currently being + * used. The URL is chosen based on the name of the puzzle. If the puzzle type is not + * recognized, a general tutorial page is opened. + */ private void helpTutorial() { // redirecting to certain help link in wiki Puzzle puzzle = GameBoardFacade.getInstance().getPuzzleModule(); @@ -639,16 +721,14 @@ private void helpTutorial() { default: url = "https://github.com/Bram-Hub/Legup/wiki/LEGUP-Tutorial"; } - Runtime rt = Runtime.getRuntime(); try { - // rt.exec("rundll32 url.dll,FileProtocolHandler "+url); java.awt.Desktop.getDesktop().browse(java.net.URI.create(url)); } catch (IOException e) { e.printStackTrace(); } } - // add the new function need to implement + // unfinished public void add_drop() { // add the mouse event then we can use the new listener to implement and // we should create a need jbuttom for it to ship the rule we select. @@ -665,7 +745,11 @@ public void actionPerformed(ActionEvent e) { panel.add(moveing_buttom); } - // Quick save proof to the current file with a pop window to show "successfully saved" + /** + * Saves the puzzle using the current file name and shows a message dialog to confirm that the + * save operation was successful. If the puzzle or exporter is null, or if an error occurs + * during export, the method will catch the exception and print the stack trace. + */ private void saveProofChange() { Puzzle puzzle = GameBoardFacade.getInstance().getPuzzleModule(); if (puzzle == null) { @@ -688,13 +772,22 @@ private void saveProofChange() { } } - // ask to edu.rpi.legup.save current proof + /** + * Displays a confirmation dialog with a specified message. Returns {@code true} if the user + * selects "No" or cancels the action, and {@code false} if the user selects "Yes". + * + * @param instr the message to display in the confirmation dialog + * @return {@code true} if the user chooses not to quit, {@code false} otherwise + */ public boolean noquit(String instr) { int n = JOptionPane.showConfirmDialog(null, instr, "Confirm", JOptionPane.YES_NO_OPTION); return n != JOptionPane.YES_OPTION; } - /** Sets the main content for the edu.rpi.legup.user interface */ + /** + * Configures the layout and components for the main user interface. This includes setting up + * panels, split panes, and borders, and adding them to the main content pane. + */ protected void setupContent() { // JPanel consoleBox = new JPanel(new BorderLayout()); JPanel treeBox = new JPanel(new BorderLayout()); @@ -727,110 +820,193 @@ protected void setupContent() { ruleBox.add(boardPanel); treeBox.add(ruleBox); this.add(treeBox); - // consoleBox.add(treeBox); - // - // getContentPane().add(consoleBox); - - // JPopupPanel popupPanel = new JPopupPanel(); - // setGlassPane(popupPanel); - // popupPanel.setVisible(true); mainPanel.setDividerLocation(mainPanel.getMaximumDividerLocation() + 100); - // frame.pack(); + revalidate(); } - private void setupToolBar() { - setToolBarButtons(new JButton[ToolbarName.values().length]); - for (int i = 0; i < ToolbarName.values().length; i++) { - String toolBarName = ToolbarName.values()[i].toString(); - URL resourceLocation = - ClassLoader.getSystemClassLoader() - .getResource("edu/rpi/legup/images/Legup/" + toolBarName + ".png"); - - // Scale the image icons down to make the buttons smaller - ImageIcon imageIcon = new ImageIcon(resourceLocation); - Image image = imageIcon.getImage(); - imageIcon = - new ImageIcon( - image.getScaledInstance( - this.TOOLBAR_ICON_SCALE, - this.TOOLBAR_ICON_SCALE, - Image.SCALE_SMOOTH)); - - JButton button = new JButton(toolBarName, imageIcon); - button.setFocusPainted(false); - getToolBarButtons()[i] = button; - } + /** + * Initializes the first toolbar, configures its appearance, and adds an 'Open' button with an + * associated icon. An action listener is attached to the button to trigger the loading of a + * puzzle when clicked. + */ + private void setupToolBar1() { + toolBar1 = new JToolBar(); + toolBar1.setFloatable(false); + toolBar1.setRollover(true); + setToolBar2Buttons(new JButton[1]); + + URL open_url = + ClassLoader.getSystemClassLoader() + .getResource("edu/rpi/legup/images/Legup/Open.png"); + + // Scale the image icons down to make the buttons smaller + ImageIcon OpenImageIcon = new ImageIcon(open_url); + Image OpenImage = OpenImageIcon.getImage(); + OpenImageIcon = + new ImageIcon( + OpenImage.getScaledInstance( + this.TOOLBAR_ICON_SCALE, + this.TOOLBAR_ICON_SCALE, + Image.SCALE_SMOOTH)); + + JButton open = new JButton("Open", OpenImageIcon); + open.setFocusPainted(false); + + open.addActionListener((ActionEvent) -> loadPuzzle()); + + getToolBar2Buttons()[0] = open; + toolBar1.add(getToolBar2Buttons()[0]); + + this.add(toolBar1, BorderLayout.NORTH); + } - toolBar = new JToolBar(); - toolBar.setFloatable(false); - toolBar.setRollover(true); + /** + * Initializes the second toolbar, configures its appearance, and adds four buttons each with + * associated icons. Action listeners are attached to each button to trigger their respective + * actions when clicked: + * + *

    + *
  • 'Directions' button triggers the `directionsToolButton` method. + *
  • 'Undo' button triggers the undo action in the puzzle's history. + *
  • 'Redo' button triggers the redo action in the puzzle's history. + *
  • 'Check' button triggers the `checkProof` method. + *
+ */ + private void setupToolBar2() { + toolBar2 = new JToolBar(); + toolBar2.setFloatable(false); + toolBar2.setRollover(true); + setToolBar2Buttons(new JButton[4]); + + URL directions_url = + ClassLoader.getSystemClassLoader() + .getResource("edu/rpi/legup/images/Legup/Directions.png"); + + ImageIcon DirectionsImageIcon = new ImageIcon(directions_url); + Image DirectionsImage = DirectionsImageIcon.getImage(); + DirectionsImageIcon = + new ImageIcon( + DirectionsImage.getScaledInstance( + this.TOOLBAR_ICON_SCALE, + this.TOOLBAR_ICON_SCALE, + Image.SCALE_SMOOTH)); + + JButton directions = new JButton("Directions", DirectionsImageIcon); + directions.setFocusPainted(false); + directions.addActionListener((ActionEvent) -> directionsToolButton()); + + getToolBar2Buttons()[0] = directions; + toolBar2.add(getToolBar2Buttons()[0]); + + URL undo_url = + ClassLoader.getSystemClassLoader() + .getResource("edu/rpi/legup/images/Legup/Undo.png"); + + ImageIcon UndoImageIcon = new ImageIcon(undo_url); + Image UndoImage = UndoImageIcon.getImage(); + UndoImageIcon = + new ImageIcon( + UndoImage.getScaledInstance( + this.TOOLBAR_ICON_SCALE, + this.TOOLBAR_ICON_SCALE, + Image.SCALE_SMOOTH)); + + JButton undo = new JButton("Undo", UndoImageIcon); + undo.setFocusPainted(false); + undo.addActionListener((ActionEvent) -> GameBoardFacade.getInstance().getHistory().undo()); - for (int i = 0; i < getToolBarButtons().length; i++) { - for (int s = 0; s < TOOLBAR_SEPARATOR_BEFORE.length; s++) { - if (i == TOOLBAR_SEPARATOR_BEFORE[s]) { - toolBar.addSeparator(); - } - } - String toolBarName = ToolbarName.values()[i].toString(); + getToolBar2Buttons()[1] = undo; + toolBar2.add(getToolBar2Buttons()[1]); + + URL redo_url = + ClassLoader.getSystemClassLoader() + .getResource("edu/rpi/legup/images/Legup/Redo.png"); + + ImageIcon RedoImageIcon = new ImageIcon(redo_url); + Image RedoImage = RedoImageIcon.getImage(); + RedoImageIcon = + new ImageIcon( + RedoImage.getScaledInstance( + this.TOOLBAR_ICON_SCALE, + this.TOOLBAR_ICON_SCALE, + Image.SCALE_SMOOTH)); + + JButton redo = new JButton("Redo", RedoImageIcon); + redo.setFocusPainted(false); + redo.addActionListener( + (ActionEvent) -> { + GameBoardFacade.getInstance().getHistory().redo(); + }); - toolBar.add(getToolBarButtons()[i]); - getToolBarButtons()[i].setToolTipText(toolBarName); + getToolBar2Buttons()[2] = redo; + toolBar2.add(getToolBar2Buttons()[2]); - getToolBarButtons()[i].setVerticalTextPosition(SwingConstants.BOTTOM); - getToolBarButtons()[i].setHorizontalTextPosition(SwingConstants.CENTER); - } + URL check_url = + ClassLoader.getSystemClassLoader() + .getResource("edu/rpi/legup/images/Legup/Check.png"); + + ImageIcon CheckImageIcon = new ImageIcon(check_url); + Image CheckImage = CheckImageIcon.getImage(); + CheckImageIcon = + new ImageIcon( + CheckImage.getScaledInstance( + this.TOOLBAR_ICON_SCALE, + this.TOOLBAR_ICON_SCALE, + Image.SCALE_SMOOTH)); + + JButton check = new JButton("Check", CheckImageIcon); + check.setFocusPainted(false); + check.addActionListener((ActionEvent) -> checkProof()); + + getToolBar2Buttons()[3] = check; + toolBar2.add(getToolBar2Buttons()[3]); + + this.add(toolBar2, BorderLayout.NORTH); + } - // toolBarButtons[ToolbarName.OPEN_PUZZLE.ordinal()].addActionListener((ActionEvent - // e) -> - // promptPuzzle()); - // toolBarButtons[ToolbarName.SAVE.ordinal()].addActionListener((ActionEvent e) -> - // saveProof()); - // toolBarButtons[ToolbarName.UNDO.ordinal()].addActionListener((ActionEvent e) -> - // GameBoardFacade.getInstance().getHistory().undo()); - // toolBarButtons[ToolbarName.REDO.ordinal()].addActionListener((ActionEvent e) -> - // GameBoardFacade.getInstance().getHistory().redo()); - toolBarButtons[ToolbarName.HINT.ordinal()].addActionListener((ActionEvent e) -> {}); - toolBarButtons[ToolbarName.CHECK.ordinal()].addActionListener( - (ActionEvent e) -> checkProof()); - toolBarButtons[ToolbarName.SUBMIT.ordinal()].addActionListener((ActionEvent e) -> {}); - toolBarButtons[ToolbarName.DIRECTIONS.ordinal()].addActionListener((ActionEvent e) -> {}); - - toolBarButtons[ToolbarName.CHECK_ALL.ordinal()].addActionListener( - (ActionEvent e) -> checkProofAll()); - - // toolBarButtons[ToolbarName.SAVE.ordinal()].setEnabled(false); - // toolBarButtons[ToolbarName.UNDO.ordinal()].setEnabled(false); - // toolBarButtons[ToolbarName.REDO.ordinal()].setEnabled(false); - toolBarButtons[ToolbarName.HINT.ordinal()].setEnabled(false); - toolBarButtons[ToolbarName.CHECK.ordinal()].setEnabled(false); - toolBarButtons[ToolbarName.SUBMIT.ordinal()].setEnabled(false); - toolBarButtons[ToolbarName.DIRECTIONS.ordinal()].setEnabled(false); - toolBarButtons[ToolbarName.CHECK_ALL.ordinal()].setEnabled(true); - - this.add(toolBar, BorderLayout.NORTH); + /** + * Sets the toolbar1 buttons + * + * @param toolBar1Buttons toolbar buttons + */ + public void setToolBar1Buttons(JButton[] toolBar1Buttons) { + this.toolBar1Buttons = toolBar1Buttons; + } + + /** + * Sets the toolbar2 buttons + * + * @param toolBar2Buttons toolbar buttons + */ + public void setToolBar2Buttons(JButton[] toolBar2Buttons) { + this.toolBar2Buttons = toolBar2Buttons; } /** - * Sets the toolbar buttons + * Gets the toolbar1 buttons * - * @param toolBarButtons toolbar buttons + * @return toolbar1 buttons */ - public void setToolBarButtons(JButton[] toolBarButtons) { - this.toolBarButtons = toolBarButtons; + public JButton[] getToolBar1Buttons() { + return toolBar1Buttons; } /** - * Gets the toolbar buttons + * Gets the toolbar2 buttons * - * @return toolbar buttons + * @return toolbar2 buttons */ - public JButton[] getToolBarButtons() { - return toolBarButtons; + public JButton[] getToolBar2Buttons() { + return toolBar2Buttons; } - /** Checks the proof for correctness */ + /** + * Uses the {@link GameBoardFacade} to obtain the current puzzle and board. If the puzzle is + * complete, it notifies the user of a correct proof. If not, it alerts the user that the board + * is not solved. + */ private void checkProof() { GameBoardFacade facade = GameBoardFacade.getInstance(); Tree tree = GameBoardFacade.getInstance().getTree(); @@ -857,11 +1033,61 @@ private void checkProof() { } } + /** + * Retrieves the puzzle name from the `GameBoardFacade` and opens a corresponding rules page in + * the default web browser. + * + * @throws IOException if an error occurs while trying to open the web page + */ + private void directionsToolButton() { + String puzzleName = GameBoardFacade.getInstance().getPuzzleModule().getName(); + // System.out.println(puzzleName); + try { + if (puzzleName.equals("Fillapix")) { + java.awt.Desktop.getDesktop() + .browse( + URI.create( + "https://github.com/Bram-Hub/LEGUP/wiki/Fill-a-pix-rules")); + } else if (puzzleName.equals("LightUp")) { + java.awt.Desktop.getDesktop() + .browse( + URI.create( + "https://github.com/Bram-Hub/LEGUP/wiki/Light-up-rules")); + } else if (puzzleName.equals("TreeTent")) { + java.awt.Desktop.getDesktop() + .browse( + URI.create( + "https://github.com/Bram-Hub/LEGUP/wiki/Tree-tent-rules")); + } else if (puzzleName.equals("ShortTruthTables")) { + java.awt.Desktop.getDesktop() + .browse( + URI.create( + "https://github.com/Bram-Hub/LEGUP/wiki/Short-truth-table-rules")); + } else { + java.awt.Desktop.getDesktop() + .browse( + URI.create( + "https://github.com/Bram-Hub/LEGUP/wiki/" + + puzzleName + + "-rules")); + } + } catch (IOException e) { + LOGGER.error("Can't open web page"); + } + } + + /** Repaints the board view and tree panel */ private void repaintAll() { boardView.repaint(); treePanel.repaint(); } + /** + * Initializes the dynamic board view, updates the tree panel, and sets rules and search panels + * based on the provided puzzle. It also updates toolbars and reloads the GUI. + * + * @param puzzle the puzzle to be displayed + */ public void setPuzzleView(Puzzle puzzle) { this.boardView = puzzle.getBoardView(); @@ -885,16 +1111,19 @@ public void setPuzzleView(Puzzle puzzle) { ruleFrame.getContradictionPanel().setRules(puzzle.getContradictionRules()); ruleFrame.getSearchPanel().setSearchBar(puzzle); - toolBarButtons[ToolbarName.CHECK.ordinal()].setEnabled(true); - // toolBarButtons[ToolbarName.SAVE.ordinal()].setEnabled(true); - + toolBar1.setVisible(false); + setupToolBar2(); reloadGui(); } + /** Calls {@code repaintTree()} to refresh the tree view. */ public void reloadGui() { repaintTree(); } + /** + * Updates the tree view displayed in the tree panel to reflect the current state of the tree. + */ public void repaintTree() { treePanel.repaintTreeView(GameBoardFacade.getInstance().getTree()); } @@ -949,6 +1178,15 @@ private boolean basicCheckProof(int[][] origCells) { return false; } + /** + * Traverses a given directory, grades the proofs found in the directory, and writes the results + * to the specified CSV writer. + * + * @param folder the folder to traverse + * @param writer the CSV writer + * @param path the current path in the directory traversal + * @throws IOException if an error occurs while writing to the CSV file + */ private void traverseDir(File folder, BufferedWriter writer, String path) throws IOException { // Recursively traverse directory GameBoardFacade facade = GameBoardFacade.getInstance(); @@ -1003,20 +1241,35 @@ private void traverseDir(File folder, BufferedWriter writer, String path) throws } } + /** + * Returns the current board view. + * + * @return the current {@link BoardView} + */ public BoardView getBoardView() { return boardView; } + /** + * Returns the current dynamic board view. + * + * @return the current {@link DynamicView} + */ public DynamicView getDynamicBoardView() { return dynamicBoardView; } + /** + * Returns the current tree panel. + * + * @return the current {@link TreePanel} + */ public TreePanel getTreePanel() { return treePanel; } /** - * Called when a action is pushed onto the edu.rpi.legup.history stack + * Called when an action is pushed onto the edu.rpi.legup.history stack * * @param command action to push onto the stack */ @@ -1024,23 +1277,19 @@ public TreePanel getTreePanel() { public void onPushChange(ICommand command) { LOGGER.info("Pushing " + command.getClass().getSimpleName() + " to stack."); undo.setEnabled(true); - // toolBarButtons[ToolbarName.UNDO.ordinal()].setEnabled(true); redo.setEnabled(false); - // toolBarButtons[ToolbarName.REDO.ordinal()].setEnabled(false); String puzzleName = GameBoardFacade.getInstance().getPuzzleModule().getName(); File puzzleFile = new File(GameBoardFacade.getInstance().getCurFileName()); frame.setTitle(puzzleName + " - " + puzzleFile.getName() + " *"); } - /** Called when the history is cleared */ + /** + * Updates the state of the undo and redo buttons to reflect that there are no actions available + * to undo or redo. It disables both buttons when the history is cleared. + */ @Override - public void onClearHistory() { - // undo.setEnabled(false); - // toolBarButtons[ToolbarName.UNDO.ordinal()].setEnabled(false); - // redo.setEnabled(false); - // toolBarButtons[ToolbarName.REDO.ordinal()].setEnabled(false); - } + public void onClearHistory() {} /** * Called when an action is redone @@ -1051,9 +1300,7 @@ public void onClearHistory() { @Override public void onRedo(boolean isBottom, boolean isTop) { undo.setEnabled(!isBottom); - // toolBarButtons[ToolbarName.UNDO.ordinal()].setEnabled(!isBottom); redo.setEnabled(!isTop); - // toolBarButtons[ToolbarName.REDO.ordinal()].setEnabled(!isTop); if (isBottom) { String puzzleName = GameBoardFacade.getInstance().getPuzzleModule().getName(); File puzzleFile = new File(GameBoardFacade.getInstance().getCurFileName()); @@ -1074,9 +1321,7 @@ public void onRedo(boolean isBottom, boolean isTop) { @Override public void onUndo(boolean isBottom, boolean isTop) { undo.setEnabled(!isBottom); - // toolBarButtons[ToolbarName.UNDO.ordinal()].setEnabled(!isBottom); redo.setEnabled(!isTop); - // toolBarButtons[ToolbarName.REDO.ordinal()].setEnabled(!isTop); String puzzleName = GameBoardFacade.getInstance().getPuzzleModule().getName(); File puzzleFile = new File(GameBoardFacade.getInstance().getCurFileName()); if (isBottom) { @@ -1120,6 +1365,7 @@ public void showStatus(String status, boolean error, int timer) { // TODO: implement } + /** Zooms the tree view to fit within the available screen space */ protected void fitTreeViewToScreen() { this.treePanel.getTreeView().zoomFit(); } diff --git a/src/main/java/edu/rpi/legup/ui/PuzzleEditorPanel.java b/src/main/java/edu/rpi/legup/ui/PuzzleEditorPanel.java index f50c8d6fc..7c2ba06ff 100644 --- a/src/main/java/edu/rpi/legup/ui/PuzzleEditorPanel.java +++ b/src/main/java/edu/rpi/legup/ui/PuzzleEditorPanel.java @@ -28,18 +28,25 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +/** + * Represents the panel used for puzzle editor in the LEGUP. This panel includes a variety of UI + * components such as toolbars, menus, and split panes. It handles puzzle file operations, including + * creating and editing puzzles. + */ public class PuzzleEditorPanel extends LegupPanel implements IHistoryListener { private static final Logger LOGGER = LogManager.getLogger(PuzzleEditorPanel.class.getName()); private JMenu[] menus; private JMenuItem helpLegup, aboutLegup; private JMenuBar menuBar; - private JToolBar toolBar; + private JToolBar toolBar1; + private JToolBar toolBar2; private JFileChooser folderBrowser; private JFrame frame; private JButton[] buttons; JSplitPane splitPanel; - private JButton[] toolBarButtons; + private JButton[] toolBar1Buttons; + private JButton[] toolBar2Buttons; private JPanel elementPanel; private DynamicView dynamicBoardView; private BoardView boardView; @@ -51,8 +58,20 @@ public class PuzzleEditorPanel extends LegupPanel implements IHistoryListener { private JPanel treePanel; private LegupUI legupUI; private EditorElementController editorElementController; - static final int[] TOOLBAR_SEPARATOR_BEFORE = {2, 4, 8}; - + private CreatePuzzleDialog cpd; + private HomePanel hp; + private boolean existingPuzzle; + private String fileName; + private File puzzleFile; + + /** + * Constructs a {@code PuzzleEditorPanel} with the specified file dialog, frame, and Legup UI + * instance + * + * @param fileDialog the file dialog used for file operations + * @param frame the main application frame + * @param legupUI the Legup UI instance + */ public PuzzleEditorPanel(FileDialog fileDialog, JFrame frame, LegupUI legupUI) { this.fileDialog = fileDialog; this.frame = frame; @@ -61,6 +80,10 @@ public PuzzleEditorPanel(FileDialog fileDialog, JFrame frame, LegupUI legupUI) { setPreferredSize(new Dimension(800, 700)); } + /** + * Sets up the content of the panel, including the layout and UI components. Initializes and + * configures the {@code DynamicView} and {@code ElementFrame}, and adds them to the panel. + */ protected void setupContent() { JSplitPane splitPanel; JPanel elementBox = new JPanel(new BorderLayout()); @@ -92,6 +115,10 @@ protected void setupContent() { revalidate(); } + /** + * Configures the menu bar with menus and menu items for the application. Adds actions for + * opening, creating, and exiting puzzles. Also sets up help and about menu items. + */ public void setMenuBar() { String os = LegupUI.getOS(); menuBar = new JMenuBar(); @@ -103,26 +130,31 @@ public void setMenuBar() { menus[0] = new JMenu("File"); // file>new - JMenuItem newPuzzle = new JMenuItem("New"); - newPuzzle.addActionListener((ActionEvent) -> loadPuzzle()); + JMenuItem openPuzzle = new JMenuItem("Open"); + openPuzzle.addActionListener((ActionEvent) -> loadPuzzle()); if (os.equals("mac")) { - newPuzzle.setAccelerator( + openPuzzle.setAccelerator( KeyStroke.getKeyStroke( - 'N', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + 'O', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); } else { - newPuzzle.setAccelerator(KeyStroke.getKeyStroke('N', InputEvent.CTRL_DOWN_MASK)); + openPuzzle.setAccelerator(KeyStroke.getKeyStroke('O', InputEvent.CTRL_DOWN_MASK)); } - // file>save - JMenuItem savePuzzle = new JMenuItem("Save As"); - savePuzzle.addActionListener((ActionEvent) -> savePuzzle()); - JMenuItem directSavePuzzle = new JMenuItem("Direct Save Proof "); - directSavePuzzle.addActionListener((ActionEvent) -> direct_save()); + // file>create + JMenuItem createPuzzle = new JMenuItem("Create"); + createPuzzle.addActionListener( + (ActionEvent) -> { + hp = new HomePanel(this.frame, this.legupUI); + cpd = new CreatePuzzleDialog(this.frame, hp); + cpd.setLocationRelativeTo(null); + cpd.setVisible(true); + existingPuzzle = false; + }); if (os.equals("mac")) { - newPuzzle.setAccelerator( + createPuzzle.setAccelerator( KeyStroke.getKeyStroke( - 'D', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + 'C', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); } else { - newPuzzle.setAccelerator(KeyStroke.getKeyStroke('D', InputEvent.CTRL_DOWN_MASK)); + createPuzzle.setAccelerator(KeyStroke.getKeyStroke('C', InputEvent.CTRL_DOWN_MASK)); } JMenuItem exit = new JMenuItem("Exit"); @@ -134,9 +166,9 @@ public void setMenuBar() { } else { exit.setAccelerator(KeyStroke.getKeyStroke('Q', InputEvent.CTRL_DOWN_MASK)); } - menus[0].add(newPuzzle); - menus[0].add(savePuzzle); - menus[0].add(directSavePuzzle); + menus[0].add(openPuzzle); + menus[0].add(createPuzzle); + // menus[0].add(directSavePuzzle); menus[0].add(exit); // EDIT @@ -147,7 +179,8 @@ public void setMenuBar() { redo = new JMenuItem("Redo"); fitBoardToScreen = new JMenuItem("Fit Board to Screen"); - menus[1].add(undo); + // TODO: Undo operation currently does not get updated correctly in history + // menus[1].add(undo); undo.addActionListener((ActionEvent) -> GameBoardFacade.getInstance().getHistory().undo()); if (os.equals("mac")) { undo.setAccelerator( @@ -157,8 +190,8 @@ public void setMenuBar() { undo.setAccelerator(KeyStroke.getKeyStroke('Z', InputEvent.CTRL_DOWN_MASK)); } - menus[1].add(redo); - + // TODO: Redo operation currently does not get updated correctly in history + // menus[1].add(redo); // Created action to support two keybinds (CTRL-SHIFT-Z, CTRL-Y) Action redoAction = new AbstractAction() { @@ -232,6 +265,11 @@ public void actionPerformed(ActionEvent e) { frame.setJMenuBar(menuBar); } + /** + * Exits the puzzle editor and resets the application state to its initial condition. This + * method clears the current puzzle from the {@code GameBoardFacade}, resets the display to the + * initial panel, and nullifies references to the tree panel and board view. + */ public void exitEditor() { // Wipes the puzzle entirely as if LEGUP just started GameBoardFacade.getInstance().clearPuzzle(); @@ -240,118 +278,192 @@ public void exitEditor() { boardView = null; } + /** + * Makes the panel visible by setting up the toolbar, content, and menu bar. This method is + * called to refresh the panel's user interface. + */ @Override public void makeVisible() { this.removeAll(); - - setupToolBar(); + setupToolBar1(); setupContent(); setMenuBar(); } - private void setupToolBar() { - setToolBarButtons(new JButton[ToolbarName.values().length + 1]); - int lastone = 0; - for (int i = 0; i < ToolbarName.values().length - 1; i++) { - String toolBarName = ToolbarName.values()[i].toString(); - URL resourceLocation = - ClassLoader.getSystemClassLoader() - .getResource("edu/rpi/legup/images/Legup/" + toolBarName + ".png"); - - // Scale the image icons down to make the buttons smaller - ImageIcon imageIcon = new ImageIcon(resourceLocation); - Image image = imageIcon.getImage(); - imageIcon = - new ImageIcon( - image.getScaledInstance( - this.TOOLBAR_ICON_SCALE, - this.TOOLBAR_ICON_SCALE, - Image.SCALE_SMOOTH)); - - JButton button = new JButton(toolBarName, imageIcon); - button.setFocusPainted(false); - getToolBarButtons()[i] = button; - lastone = i; - } + /** + * Sets up the first toolbar with buttons for opening and creating puzzles. This method + * initializes the toolbar buttons with their icons and actions. + */ + private void setupToolBar1() { + setToolBar1Buttons(new JButton[2]); - URL check_and_save = + URL open_url = ClassLoader.getSystemClassLoader() - .getResource("edu/rpi/legup/images/Legup/Check.png"); - ImageIcon imageIcon = new ImageIcon(check_and_save); - Image image = imageIcon.getImage(); - imageIcon = + .getResource("edu/rpi/legup/images/Legup/Open.png"); + ImageIcon OpenImageIcon = new ImageIcon(open_url); + Image OpenImage = OpenImageIcon.getImage(); + OpenImageIcon = new ImageIcon( - image.getScaledInstance( + OpenImage.getScaledInstance( this.TOOLBAR_ICON_SCALE, this.TOOLBAR_ICON_SCALE, Image.SCALE_SMOOTH)); - JButton checkandsave = new JButton("check and Save", imageIcon); - checkandsave.setFocusPainted(false); - checkandsave.addActionListener( - new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - // savePuzzle(); - String filename = savePuzzle(); - File puzzlename = new File(filename); - System.out.println(filename); - - GameBoardFacade.getInstance().getLegupUI().displayPanel(1); - GameBoardFacade.getInstance() - .getLegupUI() - .getProofEditor() - .loadPuzzle(filename, new File(filename)); - String puzzleName = - GameBoardFacade.getInstance().getPuzzleModule().getName(); - frame.setTitle(puzzleName + " - " + puzzlename.getName()); + JButton open = new JButton("Open", OpenImageIcon); + open.setFocusPainted(false); + open.addActionListener((ActionEvent) -> loadPuzzle()); + + getToolBar1Buttons()[0] = open; + + toolBar1 = new JToolBar(); + toolBar1.setFloatable(false); + toolBar1.setRollover(true); + toolBar1.add(getToolBar1Buttons()[0]); + + URL create_url = + ClassLoader.getSystemClassLoader() + .getResource("edu/rpi/legup/images/Legup/Open Puzzle.png"); + ImageIcon CreateImageIcon = new ImageIcon(create_url); + Image CreateImage = CreateImageIcon.getImage(); + CreateImageIcon = + new ImageIcon( + CreateImage.getScaledInstance( + this.TOOLBAR_ICON_SCALE, + this.TOOLBAR_ICON_SCALE, + Image.SCALE_SMOOTH)); + + JButton create = new JButton("Create", CreateImageIcon); + create.setFocusPainted(false); + create.addActionListener( + (ActionEvent) -> { + hp = new HomePanel(this.frame, this.legupUI); + cpd = new CreatePuzzleDialog(this.frame, hp); + cpd.setLocationRelativeTo(null); + cpd.setVisible(true); + existingPuzzle = false; + }); + getToolBar1Buttons()[1] = create; + + toolBar1.setFloatable(false); + toolBar1.setRollover(true); + toolBar1.add(getToolBar1Buttons()[1]); + + this.add(toolBar1, BorderLayout.NORTH); + } + + /** + * Sets up the second toolbar with buttons for resetting, saving, and saving & solving puzzles. + * This method initializes the toolbar buttons with their icons and actions. + */ + private void setupToolBar2() { + toolBar2 = new JToolBar(); + toolBar2.setFloatable(false); + toolBar2.setRollover(true); + setToolBar2Buttons(new JButton[3]); + + URL reset = + ClassLoader.getSystemClassLoader() + .getResource("edu/rpi/legup/images/Legup/Reset.png"); + ImageIcon ResetImageIcon = new ImageIcon(reset); + Image ResetImage = ResetImageIcon.getImage(); + ResetImageIcon = + new ImageIcon( + ResetImage.getScaledInstance( + this.TOOLBAR_ICON_SCALE, + this.TOOLBAR_ICON_SCALE, + Image.SCALE_SMOOTH)); + + JButton resetButton = new JButton("Reset", ResetImageIcon); + resetButton.setFocusPainted(false); + + resetButton.addActionListener( + a -> { + if (existingPuzzle) { + legupUI.getPuzzleEditor().loadPuzzle(fileName, puzzleFile); + } else { + if (cpd.getGame().equals("ShortTruthTable")) { + GameBoardFacade.getInstance() + .loadPuzzle(cpd.getGame(), cpd.getTextArea()); + } else { + GameBoardFacade.getInstance() + .loadPuzzle( + cpd.getGame(), + Integer.valueOf(cpd.getRows()), + Integer.valueOf(cpd.getColumns())); + } } }); - getToolBarButtons()[lastone + 1] = checkandsave; - System.out.println("it is create new file"); - toolBar = new JToolBar(); - toolBar.setFloatable(false); - toolBar.setRollover(true); + getToolBar2Buttons()[0] = resetButton; + toolBar2.add(getToolBar2Buttons()[0]); - for (int i = 0; i < getToolBarButtons().length - 1; i++) { - for (int s = 0; s < TOOLBAR_SEPARATOR_BEFORE.length; s++) { - if (i == TOOLBAR_SEPARATOR_BEFORE[s]) { - toolBar.addSeparator(); - } - } - String toolBarName = ToolbarName.values()[i].toString(); + URL save_as = + ClassLoader.getSystemClassLoader() + .getResource("edu/rpi/legup/images/Legup/Save.png"); + ImageIcon SaveAsImageIcon = new ImageIcon(save_as); + Image SaveAsImage = SaveAsImageIcon.getImage(); + SaveAsImageIcon = + new ImageIcon( + SaveAsImage.getScaledInstance( + this.TOOLBAR_ICON_SCALE, + this.TOOLBAR_ICON_SCALE, + Image.SCALE_SMOOTH)); - toolBar.add(getToolBarButtons()[i]); - getToolBarButtons()[i].setToolTipText(toolBarName); + JButton saveas = new JButton("Save As", SaveAsImageIcon); + saveas.setFocusPainted(false); + saveas.addActionListener((ActionEvent) -> savePuzzle()); - getToolBarButtons()[i].setVerticalTextPosition(SwingConstants.BOTTOM); - getToolBarButtons()[i].setHorizontalTextPosition(SwingConstants.CENTER); - } + getToolBar2Buttons()[1] = saveas; + toolBar2.add(getToolBar2Buttons()[1]); + + URL save_and_solve = + ClassLoader.getSystemClassLoader() + .getResource("edu/rpi/legup/images/Legup/Check.png"); + ImageIcon SaveSolveImageIcon = new ImageIcon(save_and_solve); + Image SaveSolveImage = SaveSolveImageIcon.getImage(); + SaveSolveImageIcon = + new ImageIcon( + SaveSolveImage.getScaledInstance( + this.TOOLBAR_ICON_SCALE, + this.TOOLBAR_ICON_SCALE, + Image.SCALE_SMOOTH)); + + JButton saveandsolve = new JButton("Save & Solve", SaveSolveImageIcon); + saveandsolve.setFocusPainted(false); + saveandsolve.addActionListener( + new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (GameBoardFacade.getInstance().getPuzzleModule() != null) { + String filename = savePuzzle(); + File puzzlename = new File(filename); + System.out.println(filename); + + GameBoardFacade.getInstance().getLegupUI().displayPanel(1); + GameBoardFacade.getInstance() + .getLegupUI() + .getProofEditor() + .loadPuzzle(filename, new File(filename)); + String puzzleName = + GameBoardFacade.getInstance().getPuzzleModule().getName(); + frame.setTitle(puzzleName + " - " + puzzlename.getName()); + } + } + }); + getToolBar2Buttons()[2] = saveandsolve; + toolBar2.add(getToolBar2Buttons()[2]); - // toolBarButtons[ToolbarName.OPEN_PUZZLE.ordinal()].addActionListener((ActionEvent - // e) -> - // promptPuzzle()); - // toolBarButtons[ToolbarName.SAVE.ordinal()].addActionListener((ActionEvent e) -> - // saveProof()); - // toolBarButtons[ToolbarName.UNDO.ordinal()].addActionListener((ActionEvent e) -> - // GameBoardFacade.getInstance().getHistory().undo()); - // toolBarButtons[ToolbarName.REDO.ordinal()].addActionListener((ActionEvent e) -> - // GameBoardFacade.getInstance().getHistory().redo()); - toolBarButtons[ToolbarName.HINT.ordinal()].addActionListener((ActionEvent e) -> {}); - toolBarButtons[ToolbarName.SUBMIT.ordinal()].addActionListener((ActionEvent e) -> {}); - toolBarButtons[ToolbarName.DIRECTIONS.ordinal()].addActionListener((ActionEvent e) -> {}); - - // toolBarButtons[ToolbarName.SAVE.ordinal()].setEnabled(false); - // toolBarButtons[ToolbarName.UNDO.ordinal()].setEnabled(false); - // toolBarButtons[ToolbarName.REDO.ordinal()].setEnabled(false); - toolBarButtons[ToolbarName.HINT.ordinal()].setEnabled(false); - toolBarButtons[ToolbarName.SUBMIT.ordinal()].setEnabled(false); - toolBarButtons[ToolbarName.DIRECTIONS.ordinal()].setEnabled(false); - - this.add(toolBar, BorderLayout.NORTH); + this.add(toolBar2, BorderLayout.NORTH); } + /** + * Initializes a puzzle based on the provided game name, rows, and columns. + * + * @param game the name of the game or puzzle to load + * @param rows the number of rows in the puzzle + * @param columns the number of columns in the puzzle + * @throws IllegalArgumentException if the provided arguments are invalid + */ public void loadPuzzleFromHome(String game, int rows, int columns) throws IllegalArgumentException { GameBoardFacade facade = GameBoardFacade.getInstance(); @@ -365,6 +477,13 @@ public void loadPuzzleFromHome(String game, int rows, int columns) } } + /** + * Initializes a puzzle based on the provided game name and an array of statements. + * + * @param game the name of the game or puzzle to load + * @param statements an array of statements to initialize the puzzle + * @throws IllegalArgumentException if the provided arguments are invalid + */ public void loadPuzzleFromHome(String game, String[] statements) { GameBoardFacade facade = GameBoardFacade.getInstance(); try { @@ -377,11 +496,18 @@ public void loadPuzzleFromHome(String game, String[] statements) { } } - // File opener + /** + * Prompts the user to select a puzzle file to open. Opens a file chooser dialog and returns the + * selected file's name and file object. If a puzzle is currently loaded, prompts the user to + * confirm if they want to open a new puzzle. + * + * @return an array containing the selected file name and file object, or null if the operation + * was canceled + */ public Object[] promptPuzzle() { GameBoardFacade facade = GameBoardFacade.getInstance(); if (facade.getBoard() != null) { - if (noQuit("Opening a new puzzle?")) { + if (noQuit("Open an existing puzzle?")) { return new Object[0]; } } @@ -405,7 +531,6 @@ public Object[] promptPuzzle() { fileBrowser.setAcceptAllFileFilterUsed(false); File puzzlePath = fileBrowser.getSelectedFile(); - System.out.println(puzzlePath.getAbsolutePath()); if (puzzlePath != null) { fileName = puzzlePath.getAbsolutePath(); @@ -420,6 +545,11 @@ public Object[] promptPuzzle() { return new Object[] {fileName, puzzleFile}; } + /** + * Loads a puzzle by prompting the user to select a puzzle file. If the user cancels the + * operation, no action is taken. If a puzzle file is selected, it will be loaded using the file + * name and file object. + */ public void loadPuzzle() { Object[] items = promptPuzzle(); // Return if items == null (cancel) @@ -431,6 +561,13 @@ public void loadPuzzle() { loadPuzzle(fileName, puzzleFile); } + /** + * Loads a puzzle from the specified file. If the puzzle file is valid and exists, it loads the + * puzzle and updates the UI. If the file format is invalid, an error message is displayed. + * + * @param fileName the name of the puzzle file + * @param puzzleFile the file object representing the puzzle file + */ public void loadPuzzle(String fileName, File puzzleFile) { if (puzzleFile != null && puzzleFile.exists()) { try { @@ -438,6 +575,9 @@ public void loadPuzzle(String fileName, File puzzleFile) { GameBoardFacade.getInstance().loadPuzzleEditor(fileName); String puzzleName = GameBoardFacade.getInstance().getPuzzleModule().getName(); frame.setTitle(puzzleName + " - " + puzzleFile.getName()); + existingPuzzle = true; + this.fileName = fileName; + this.puzzleFile = puzzleFile; } catch (InvalidFileFormatException e) { legupUI.displayPanel(0); LOGGER.error(e.getMessage()); @@ -451,42 +591,90 @@ public void loadPuzzle(String fileName, File puzzleFile) { } } + /** + * Displays a confirmation dialog with the given instruction message. The method returns true if + * the user selected "No" or cancelled the dialog, and false if the user selected "Yes". + * + * @param instr the instruction message to display in the confirmation dialog + * @return true if the user selected "No" or canceled; false if the user selected "Yes" + */ public boolean noQuit(String instr) { int n = JOptionPane.showConfirmDialog(null, instr, "Confirm", JOptionPane.YES_NO_OPTION); return n != JOptionPane.YES_OPTION; } + /** {@inheritDoc} */ @Override public void onPushChange(ICommand command) {} + /** {@inheritDoc} */ @Override public void onUndo(boolean isBottom, boolean isTop) {} + /** {@inheritDoc} */ @Override public void onRedo(boolean isBottom, boolean isTop) {} + /** {@inheritDoc} */ @Override - public void onClearHistory() { - // undo.setEnabled(false); - // redo.setEnabled(false); - } + public void onClearHistory() {} + /** + * Returns the current board view + * + * @return the board view + */ public BoardView getBoardView() { return boardView; } - public JButton[] getToolBarButtons() { - return toolBarButtons; + /** + * Returns the array of buttons for the first toolbar + * + * @return the array of toolbar1 buttons + */ + public JButton[] getToolBar1Buttons() { + return toolBar1Buttons; + } + + /** + * Sets the array of buttons for the first toolbar + * + * @param toolBar1Buttons the array of toolbar1 buttons + */ + public void setToolBar1Buttons(JButton[] toolBar1Buttons) { + this.toolBar1Buttons = toolBar1Buttons; } - public void setToolBarButtons(JButton[] toolBarButtons) { - this.toolBarButtons = toolBarButtons; + /** + * Returns the array of buttons for the second toolbar + * + * @return the array of toolbar2 buttons + */ + public JButton[] getToolBar2Buttons() { + return toolBar2Buttons; } + /** + * Sets the array of buttons for the second toolbar + * + * @param toolBar2Buttons the array of toolbar2 buttons + */ + public void setToolBar2Buttons(JButton[] toolBar2Buttons) { + this.toolBar2Buttons = toolBar2Buttons; + } + + /** Repaints the current board view */ private void repaintAll() { boardView.repaint(); } + /** + * Sets the puzzle view based on the provided puzzle object. Updates the UI components to + * display the new puzzle. + * + * @param puzzle the puzzle object to display + */ public void setPuzzleView(Puzzle puzzle) { this.boardView = puzzle.getBoardView(); editorElementController.setElementController(boardView.getElementController()); @@ -502,13 +690,11 @@ public void setPuzzleView(Puzzle puzzle) { dynamicBoardView.setBorder(titleBoard); puzzle.addBoardListener(puzzle.getBoardView()); - System.out.println("Setting elements"); if (this.elementFrame != null) { elementFrame.setElements(puzzle); } - - toolBarButtons[ToolbarName.CHECK.ordinal()].setEnabled(true); - // toolBarButtons[ToolbarName.SAVE.ordinal()].setEnabled(true); + toolBar1.setVisible(false); + setupToolBar2(); } /** Saves a puzzle */ @@ -531,6 +717,13 @@ private void direct_save() { } } + /** + * Saves the current puzzle to a user-selected directory. Prompts the user to select a directory + * and saves the puzzle to that directory. Returns the path where the puzzle was saved. + * + * @return the path where the puzzle was saved, or an empty string if the save operation was + * canceled + */ private String savePuzzle() { Puzzle puzzle = GameBoardFacade.getInstance().getPuzzleModule(); if (puzzle == null) { @@ -567,7 +760,7 @@ private String savePuzzle() { folderBrowser.setAcceptAllFileFilterUsed(false); String path = folderBrowser.getSelectedFile().getAbsolutePath(); - + preferences.setSavedPath(path); if (path != null) { try { PuzzleExporter exporter = puzzle.getExporter(); @@ -582,6 +775,11 @@ private String savePuzzle() { return path; } + /** + * Returns the current dynamic board view + * + * @return the dynamic board view + */ public DynamicView getDynamicBoardView() { return dynamicBoardView; } diff --git a/src/main/java/edu/rpi/legup/ui/ScrollView.java b/src/main/java/edu/rpi/legup/ui/ScrollView.java index 0bf8335a2..589573154 100644 --- a/src/main/java/edu/rpi/legup/ui/ScrollView.java +++ b/src/main/java/edu/rpi/legup/ui/ScrollView.java @@ -6,6 +6,11 @@ import java.util.logging.Logger; import javax.swing.*; +/** + * ScrollView extends {@link JScrollPane} to provide a customizable view with zoom and scroll + * capabilities. It uses a {@link ZoomablePane} as the canvas and allows for zooming and scrolling + * with respect to the canvas content. + */ public class ScrollView extends JScrollPane { private static final Logger LOGGER = Logger.getLogger(ScrollView.class.getName()); @@ -165,6 +170,12 @@ public void zoom(int n, Point point) { revalidate(); } + /** + * Adjusts the zoom level to the given scale and centers the viewport on the current center + * point + * + * @param newScale the new scale to set + */ public void zoomTo(double newScale) { // check zoom bounds if (newScale < minScale) { @@ -282,6 +293,11 @@ public void setSize(Dimension size) { updateSize(); } + /** + * Gets the canvas for this {@code ScrollView} + * + * @return the ZoomablePane instance used as the canvas + */ public ZoomablePane getCanvas() { return canvas; } diff --git a/src/main/java/edu/rpi/legup/ui/ToolbarName.java b/src/main/java/edu/rpi/legup/ui/ToolbarName.java index ba02ebd2e..622d16d8d 100644 --- a/src/main/java/edu/rpi/legup/ui/ToolbarName.java +++ b/src/main/java/edu/rpi/legup/ui/ToolbarName.java @@ -1,11 +1,12 @@ package edu.rpi.legup.ui; +/** + * This enum defines constants for toolbar names used in the user interface. Each represents a + * specific toolbar action. + */ public enum ToolbarName { - HINT, - CHECK, - SUBMIT, DIRECTIONS, - CHECK_ALL; + CHECK; /** * Gets the String representation of the ToolbarName enum diff --git a/src/main/java/edu/rpi/legup/ui/ZoomWidget.java b/src/main/java/edu/rpi/legup/ui/ZoomWidget.java index aa5b65c4e..40f36113f 100644 --- a/src/main/java/edu/rpi/legup/ui/ZoomWidget.java +++ b/src/main/java/edu/rpi/legup/ui/ZoomWidget.java @@ -10,6 +10,10 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +/** + * The {@code ZoomWidget} displays a zoom icon that, when clicked, shows a popup slider to adjust + * the zoom level of the associated {@code ScrollView}. + */ public class ZoomWidget extends JLabel { private ScrollView parent; private PopupSlider palette = new PopupSlider(); @@ -32,12 +36,13 @@ public ZoomWidget(ScrollView parent) { addMouseListener(open); } - /** */ + /** A {@code JPopupMenu} subclass that contains a vertical slider for adjusting zoom level. */ private class PopupSlider extends JPopupMenu implements ChangeListener { private static final long serialVersionUID = 8225019381200459814L; private JSlider slider; + /** Constructs a {@code PopupSlider} with a vertical slider */ public PopupSlider() { slider = new JSlider(SwingConstants.VERTICAL, 0, 400, 200); slider.setMajorTickSpacing(25); @@ -47,6 +52,11 @@ public PopupSlider() { slider.addChangeListener(this); } + /** + * Handles state changes in the slider by adjusting the zoom level of the {@code ScrollView} + * + * @param e the {@code ChangeEvent} indicating that the slider's state has changed + */ public void stateChanged(ChangeEvent e) { if (slider.getValueIsAdjusting()) { parent.zoomTo((double) slider.getValue() / 100.0); diff --git a/src/main/java/edu/rpi/legup/ui/ZoomablePane.java b/src/main/java/edu/rpi/legup/ui/ZoomablePane.java index 934d31c53..66af90abd 100644 --- a/src/main/java/edu/rpi/legup/ui/ZoomablePane.java +++ b/src/main/java/edu/rpi/legup/ui/ZoomablePane.java @@ -5,6 +5,10 @@ import java.awt.Graphics2D; import javax.swing.*; +/** + * The {@code ZoomablePane} class is used to display components in a zoomable and scalable manner. + * It uses {@code ScrollView} to handle scaling and drawing of the content. + */ public class ZoomablePane extends JLayeredPane { private ScrollView viewer; diff --git a/src/main/java/edu/rpi/legup/ui/boardview/BoardView.java b/src/main/java/edu/rpi/legup/ui/boardview/BoardView.java index ca03f1e25..18b47d98b 100644 --- a/src/main/java/edu/rpi/legup/ui/boardview/BoardView.java +++ b/src/main/java/edu/rpi/legup/ui/boardview/BoardView.java @@ -11,6 +11,10 @@ import java.awt.*; import java.util.ArrayList; +/** + * An abstract class representing a view for a board in the puzzle game. It handles the visual + * representation and user interactions with the board elements. + */ public abstract class BoardView extends ScrollView implements IBoardListener { protected TreeElement treeElement; protected Board board; @@ -125,6 +129,7 @@ public void setBoard(Board board) { } } + /** Configures the view to handle case interactions */ protected void setCasePickable() { CaseBoard caseBoard = (CaseBoard) board; Board baseBoard = caseBoard.getBaseBoard(); @@ -183,6 +188,11 @@ public ArrayList getElementViews() { return elementViews; } + /** + * Gets the ElementController associated with this board view. + * + * @return the ElementController + */ public ElementController getElementController() { return elementController; } @@ -192,6 +202,11 @@ public void draw(Graphics2D graphics2D) { drawBoard(graphics2D); } + /** + * Draws the board and its elements. + * + * @param graphics2D the Graphics2D context used for drawing + */ public void drawBoard(Graphics2D graphics2D) { for (ElementView element : elementViews) { element.draw(graphics2D); @@ -208,5 +223,10 @@ public void onBoardDataChanged(PuzzleElement puzzleElement) { repaint(); } + /** + * Gets the selection popup menu for this board view. + * + * @return the DataSelectionView associated with this view + */ public abstract DataSelectionView getSelectionPopupMenu(); } diff --git a/src/main/java/edu/rpi/legup/ui/boardview/DataSelectionView.java b/src/main/java/edu/rpi/legup/ui/boardview/DataSelectionView.java index cedfa08fe..bc40e3d58 100644 --- a/src/main/java/edu/rpi/legup/ui/boardview/DataSelectionView.java +++ b/src/main/java/edu/rpi/legup/ui/boardview/DataSelectionView.java @@ -5,8 +5,17 @@ import javax.swing.*; import javax.swing.border.BevelBorder; +/** + * DataSelectionView is a popup menu used for selecting data elements. It extends JPopupMenu and is + * styled with a gray background and a raised bevel border. + */ public class DataSelectionView extends JPopupMenu { + /** + * Constructs a DataSelectionView with the given controller. + * + * @param controller The ElementController to handle UI events. + */ public DataSelectionView(ElementController controller) { setBackground(Color.GRAY); setBorder(new BevelBorder(BevelBorder.RAISED)); diff --git a/src/main/java/edu/rpi/legup/ui/boardview/ElementSelection.java b/src/main/java/edu/rpi/legup/ui/boardview/ElementSelection.java index 8e6f2cb18..eaa85082d 100644 --- a/src/main/java/edu/rpi/legup/ui/boardview/ElementSelection.java +++ b/src/main/java/edu/rpi/legup/ui/boardview/ElementSelection.java @@ -3,25 +3,48 @@ import java.awt.*; import java.util.ArrayList; +/** + * ElementSelection manages the selection and hover states of ElementViews. It maintains a list of + * selected elements, the currently hovered element, and the mouse point location. + */ public class ElementSelection { private ArrayList selection; private ElementView hover; private Point mousePoint; + /** + * Constructs an ElementSelection instance with an empty selection and no hover or mouse point + */ public ElementSelection() { this.selection = new ArrayList<>(); this.hover = null; this.mousePoint = null; } + /** + * Gets the list of currently selected ElementViews. + * + * @return the list of selected ElementViews + */ public ArrayList getSelection() { return selection; } + /** + * Gets the first ElementView in the selection, or null if the selection is empty. + * + * @return the first selected ElementView, or null if there are no selections + */ public ElementView getFirstSelection() { return selection.size() == 0 ? null : selection.get(0); } + /** + * Toggles the selection state of an ElementView. If the ElementView is currently selected, it + * is deselected. Otherwise, it is selected. + * + * @param elementView the ElementView to toggle + */ public void toggleSelection(ElementView elementView) { if (selection.contains(elementView)) { selection.remove(elementView); @@ -32,12 +55,19 @@ public void toggleSelection(ElementView elementView) { } } + /** + * Sets a new selection, clearing the previous selection and selecting the specified + * ElementView. + * + * @param elementView the ElementView to select + */ public void newSelection(ElementView elementView) { clearSelection(); selection.add(elementView); elementView.setSelected(true); } + /** Clears the selection and deselects all ElementViews */ public void clearSelection() { for (ElementView elementView : selection) { elementView.setSelected(false); @@ -45,10 +75,20 @@ public void clearSelection() { selection.clear(); } + /** + * Gets the currently hovered ElementView. + * + * @return the currently hovered ElementView, or null if no element is hovered + */ public ElementView getHover() { return hover; } + /** + * Sets a new hovered ElementView, updating the hover state of the previous and new elements. + * + * @param newHovered the new ElementView to be hovered + */ public void newHover(ElementView newHovered) { newHovered.setHover(true); if (hover != null) { @@ -57,6 +97,7 @@ public void newHover(ElementView newHovered) { hover = newHovered; } + /** Clears the current hover state if there exists one */ public void clearHover() { if (hover != null) { hover.setHover(false); @@ -64,10 +105,20 @@ public void clearHover() { } } + /** + * Gets the current mouse point location. + * + * @return the current mouse point location + */ public Point getMousePoint() { return mousePoint; } + /** + * Sets the mouse point location. + * + * @param point the new mouse point location + */ public void setMousePoint(Point point) { this.mousePoint = point; } diff --git a/src/main/java/edu/rpi/legup/ui/boardview/ElementView.java b/src/main/java/edu/rpi/legup/ui/boardview/ElementView.java index 83b2cb099..eab13cc1d 100644 --- a/src/main/java/edu/rpi/legup/ui/boardview/ElementView.java +++ b/src/main/java/edu/rpi/legup/ui/boardview/ElementView.java @@ -9,6 +9,10 @@ import java.awt.image.BufferedImage; import javax.swing.*; +/** + * ElementView represents a visual representation of a PuzzleElement. It handles drawing, selection, + * hover states, and interaction with the PuzzleElement. + */ public abstract class ElementView implements Shape { protected int index; protected Point location; @@ -73,6 +77,12 @@ public void draw(Graphics2D graphics2D) { } } + /** + * Draws the basic element representation (e.g., border, text) on the provided Graphics2D + * context. + * + * @param graphics2D the Graphics2D context to use for drawing + */ public void drawElement(Graphics2D graphics2D) { graphics2D.setStroke(new BasicStroke(1)); graphics2D.draw( @@ -87,8 +97,19 @@ public void drawElement(Graphics2D graphics2D) { graphics2D.drawString(String.valueOf(puzzleElement.getData()), xText, yText); } + /** + * Draws additional elements for given PuzzleElements (default implementation does nothing). + * Overriden in some puzzle element views. + * + * @param graphics2D the Graphics2D context to use for drawing + */ public void drawGiven(Graphics2D graphics2D) {} + /** + * Draws a hover effect on the ElementView. + * + * @param graphics2D the Graphics2D context to use for drawing + */ public void drawHover(Graphics2D graphics2D) { graphics2D.setColor(hoverColor); graphics2D.setStroke(new BasicStroke(2)); @@ -97,6 +118,11 @@ public void drawHover(Graphics2D graphics2D) { location.x + 1.5f, location.y + 1.5f, size.width - 3, size.height - 3)); } + /** + * Draws a modified effect on the ElementView. + * + * @param graphics2D the Graphics2D context to use for drawing + */ public void drawModified(Graphics2D graphics2D) { graphics2D.setColor(puzzleElement.isValid() ? modifiedColor : invalidColor); graphics2D.setStroke(new BasicStroke(2)); @@ -105,6 +131,11 @@ public void drawModified(Graphics2D graphics2D) { location.x + 1.5f, location.y + 1.5f, size.width - 3, size.height - 3)); } + /** + * Draws a case rule picker on the ElementView. + * + * @param graphics2D the Graphics2D context to use for drawing + */ public void drawCase(Graphics2D graphics2D) { graphics2D.setColor(caseColor); graphics2D.fill( @@ -112,6 +143,11 @@ public void drawCase(Graphics2D graphics2D) { location.x + 1.5f, location.y + 1.5f, size.width - 3, size.height - 3)); } + /** + * Creates an image representation of the ElementView. + * + * @return a BufferedImage of the ElementView + */ public BufferedImage getImage() { BufferedImage image = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB); @@ -193,10 +229,20 @@ public void setPuzzleElement(PuzzleElement data) { this.puzzleElement = data; } + /** + * Checks if the case picker should be shown for this ElementView + * + * @return true if the case picker should be shown, false otherwise + */ public boolean isShowCasePicker() { return showCasePicker; } + /** + * Sets whether the case picker should be shown for this ElementView + * + * @param showCasePicker true if the case picker should be shown, false otherwise + */ public void setShowCasePicker(boolean showCasePicker) { this.showCasePicker = showCasePicker; } @@ -281,6 +327,14 @@ public JMenuItem getSelectionMenuItem() { return item; } + /** + * Determines if the specified point (x, y) is within the bounds of this ElementView + * + * @param x the x-coordinate of the point to check + * @param y the y-coordinate of the point to check + * @return {@code true} if the point is within the bounds of this ElementView; {@code false} + * otherwise + */ @Override public boolean contains(double x, double y) { return x >= location.x @@ -289,17 +343,43 @@ public boolean contains(double x, double y) { && y <= location.y + size.height; } + /** + * Determines if the specified Point2D object is within the bounds of this ElementView + * + * @param point the Point2D object representing the point to check + * @return {@code true} if the point is within the bounds of this ElementView; {@code false} + * otherwise + */ @Override public boolean contains(Point2D point) { return contains(point.getX(), point.getY()); } + /** + * Determines if the specified rectangle defined by (x, y, width, height) intersects with the + * bounds of this ElementView. + * + * @param x The x-coordinate of the rectangle to check + * @param y The y-coordinate of the rectangle to check + * @param width The width of the rectangle to check + * @param height The height of the rectangle to check + * @return {@code true} if the rectangle intersects with the bounds of this ElementView; {@code + * false} otherwise + */ @Override public boolean intersects(double x, double y, double width, double height) { return (x + width >= location.x && x <= location.x + size.width) || (y + height >= location.y && y <= location.y + size.height); } + /** + * Determines if the specified Rectangle2D object intersects with the bounds of this + * ElementView. + * + * @param rectangle2D the Rectangle2D object representing the rectangle to check + * @return {@code true} if the rectangle intersects with the bounds of this ElementView; {@code + * false} otherwise + */ @Override public boolean intersects(Rectangle2D rectangle2D) { return intersects( @@ -309,12 +389,31 @@ public boolean intersects(Rectangle2D rectangle2D) { rectangle2D.getHeight()); } + /** + * Determines if the specified rectangle defined by (x, y, width, height) is entirely contained + * within the bounds of this ElementView + * + * @param x the x-coordinate of the rectangle to check + * @param y the y-coordinate of the rectangle to check + * @param width the width of the rectangle to check + * @param height the height of the rectangle to check + * @return {@code true} if the rectangle is entirely contained within the bounds of this + * ElementView; {@code false} otherwise + */ @Override public boolean contains(double x, double y, double width, double height) { return (x + width >= location.x && x <= location.x + size.width) && (y + height >= location.y && y <= location.y + size.height); } + /** + * Determines if the specified Rectangle2D object is entirely contained within the bounds of + * this ElementView. + * + * @param rectangle2D the Rectangle2D object representing the rectangle to check + * @return {@code true} if the rectangle is entirely contained within the bounds of this + * ElementView; {@code false} otherwise + */ @Override public boolean contains(Rectangle2D rectangle2D) { return contains( @@ -324,22 +423,48 @@ public boolean contains(Rectangle2D rectangle2D) { rectangle2D.getHeight()); } + /** + * Returns an iterator over the path geometry of this ElementView. The iterator provides access + * to the path's segments and their coordinates, which can be used for rendering or hit testing. + * + * @param at the AffineTransform to apply to the path geometry + * @return a PathIterator that iterates over the path geometry of this ElementView + */ @Override public PathIterator getPathIterator(AffineTransform at) { return new Rectangle(location.x, location.y, size.width, size.height).getPathIterator(at); } + /** + * Returns an iterator over the path geometry of this ElementView with the specified flatness. + * The iterator provides access to the path's segments and their coordinates, which can be used + * for rendering or hit testing. + * + * @param at the AffineTransform to apply to the path geometry + * @param flatness the maximum distance that the line segments can deviate from the true path + * @return a PathIterator that iterates over the path geometry of this ElementView + */ @Override public PathIterator getPathIterator(AffineTransform at, double flatness) { return new Rectangle(location.x, location.y, size.width, size.height) .getPathIterator(at, flatness); } + /** + * Returns the bounding rectangle of this ElementView + * + * @return a Rectangle representing the bounding box of this ElementView + */ @Override public Rectangle getBounds() { return new Rectangle(location.x, location.y, size.width, size.height); } + /** + * Returns the bounding rectangle of this ElementView as a Rectangle2D + * + * @return a Rectangle2D representing the bounding box of this ElementView + */ @Override public Rectangle2D getBounds2D() { return new Rectangle(location.x, location.y, size.width, size.height); diff --git a/src/main/java/edu/rpi/legup/ui/boardview/GridBoardView.java b/src/main/java/edu/rpi/legup/ui/boardview/GridBoardView.java index c40303192..e2b8bbc12 100644 --- a/src/main/java/edu/rpi/legup/ui/boardview/GridBoardView.java +++ b/src/main/java/edu/rpi/legup/ui/boardview/GridBoardView.java @@ -5,6 +5,10 @@ import java.awt.Color; import java.awt.Dimension; +/** + * A view class for a grid-based board that displays elements in a grid layout. This class extends + * BoardView and is responsible for managing and rendering grid-based elements. + */ public class GridBoardView extends BoardView { protected Dimension gridSize; protected Dimension elementSize; @@ -51,6 +55,14 @@ public GridElementView getElement(int index) { return null; } + /** + * Retrieves the GridElementView at the specified grid coordinates (xIndex, yIndex). Returns + * null if the coordinates are out of bounds. + * + * @param xIndex the x-coordinate (column) of the element view to retrieve + * @param yIndex the y-coordinate (row) of the element view to retrieve + * @return the GridElementView at the specified coordinates, or null if out of bounds + */ public GridElementView getElement(int xIndex, int yIndex) { if (xIndex < gridSize.width && yIndex < gridSize.height) { return (GridElementView) elementViews.get(yIndex * gridSize.width + xIndex); @@ -58,7 +70,10 @@ public GridElementView getElement(int xIndex, int yIndex) { return null; } - /** Initializes the initial dimension of the viewport for the GridBoardView */ + /** + * Initializes the initial dimension of the viewport for the GridBoardView. Sets the size of the + * board view and adjusts the zoom to fit. + */ @Override public void initSize() { setSize(getProperSize()); @@ -66,9 +81,9 @@ public void initSize() { } /** - * Helper method to determine the proper dimension of the grid view + * Determines the proper dimension of the grid view based on grid size and element size. * - * @return proper dimension of the grid view + * @return the dimension of the grid view */ protected Dimension getProperSize() { Dimension boardViewSize = new Dimension(); @@ -77,10 +92,21 @@ protected Dimension getProperSize() { return boardViewSize; } + /** + * Retrieves the selection popup menu for data selection. Currently returns null as there is no + * implementation. + * + * @return null + */ public DataSelectionView getSelectionPopupMenu() { return null; } + /** + * Gets the size of each element in the grid + * + * @return the dimension of each element in the grid + */ public Dimension getElementSize() { return this.elementSize; } diff --git a/src/main/java/edu/rpi/legup/ui/boardview/GridElementView.java b/src/main/java/edu/rpi/legup/ui/boardview/GridElementView.java index 440b3a693..587ba9efb 100644 --- a/src/main/java/edu/rpi/legup/ui/boardview/GridElementView.java +++ b/src/main/java/edu/rpi/legup/ui/boardview/GridElementView.java @@ -2,6 +2,10 @@ import edu.rpi.legup.model.gameboard.GridCell; +/** + * A view class for a grid cell element in the board. This class extends ElementView and represents + * a specific type of element view associated with a GridCell. + */ public class GridElementView extends ElementView { public GridElementView(GridCell cell) { super(cell); diff --git a/src/main/java/edu/rpi/legup/ui/boardview/SelectionItemView.java b/src/main/java/edu/rpi/legup/ui/boardview/SelectionItemView.java index b2d3e31dd..6a1429a61 100644 --- a/src/main/java/edu/rpi/legup/ui/boardview/SelectionItemView.java +++ b/src/main/java/edu/rpi/legup/ui/boardview/SelectionItemView.java @@ -3,28 +3,66 @@ import edu.rpi.legup.model.gameboard.PuzzleElement; import javax.swing.*; +/** + * A menu item view class that represents a selectable item in a menu, associated with a + * PuzzleElement. This class extends JMenuItem and provides additional functionality to handle + * PuzzleElement data. + */ public class SelectionItemView extends JMenuItem { private PuzzleElement data; + /** + * Constructs a SelectionItemView with the specified PuzzleElement and icon. Initializes the + * menu item with the given icon and associates it with the provided PuzzleElement. + * + * @param data the PuzzleElement associated with this menu item + * @param icon the icon to be displayed on the menu item + */ public SelectionItemView(PuzzleElement data, Icon icon) { super(icon); this.data = data; } + /** + * Constructs a SelectionItemView with the specified PuzzleElement and display text. Initializes + * the menu item with the given display text and associates it with the provided PuzzleElement. + * + * @param data the PuzzleElement associated with this menu item + * @param display the text to be displayed on the menu item + */ public SelectionItemView(PuzzleElement data, String display) { super(display); this.data = data; } + /** + * Constructs a SelectionItemView with the specified PuzzleElement and display integer. + * Initializes the menu item with the integer converted to a string and associates it with the + * provided PuzzleElement. + * + * @param data the PuzzleElement associated with this menu item + * @param display the integer to be displayed on the menu item + */ public SelectionItemView(PuzzleElement data, int display) { super(String.valueOf(display)); this.data = data; } + /** + * Constructs a SelectionItemView with the specified PuzzleElement. Initializes the menu item + * with the data's integer representation as display text. + * + * @param data the PuzzleElement associated with this menu item + */ public SelectionItemView(PuzzleElement data) { this(data, (Integer) data.getData()); } + /** + * Gets the PuzzleElement associated with this menu item + * + * @return the PuzzleElement associated with this menu item + */ public PuzzleElement getData() { return data; } diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/CaseRulePanel.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/CaseRulePanel.java index 1fb0a16ab..4d7bb378d 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/CaseRulePanel.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/CaseRulePanel.java @@ -2,6 +2,12 @@ import javax.swing.ImageIcon; +/** + * The {@code CaseRulePanel} class is a specialized panel that represents case rules within a {@link + * RuleFrame}. It extends the {@link RulePanel} and provides specific functionality and UI + * components related to case rules. This class initializes with an icon and name that are specific + * to case rules. + */ public class CaseRulePanel extends RulePanel { /** * CaseRulePanel Constructor creates a CaseRulePanel diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/ContradictionRulePanel.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/ContradictionRulePanel.java index f695491fb..92e747305 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/ContradictionRulePanel.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/ContradictionRulePanel.java @@ -2,6 +2,12 @@ import javax.swing.*; +/** + * The {@code ContradictionRulePanel} class is a specialized panel that represents contradiction + * rules within a {@link RuleFrame}. It extends the {@link RulePanel} and provides specific + * functionality and UI components related to contradiction rules. This class initializes with an + * icon and name that are specific to contradiction rules. + */ public class ContradictionRulePanel extends RulePanel { /** * ContradictionRulePanel Constructor creates a ContradictionRulePanel diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/DirectRulePanel.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/DirectRulePanel.java index 2795f2df7..17fcac5bb 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/DirectRulePanel.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/DirectRulePanel.java @@ -4,6 +4,12 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +/** + * The {@code DirectRulePanel} class is a specialized panel that represents direct rules within a + * {@link RuleFrame}. It extends the {@link RulePanel} and provides specific functionality and UI + * components related to direct rules. This class initializes with an icon and name that are + * specific to direct rules. + */ public class DirectRulePanel extends RulePanel { private static final Logger LOGGER = LogManager.getLogger(DirectRulePanel.class.getName()); diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/RuleButton.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/RuleButton.java index e9c274250..929f211d0 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/RuleButton.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/RuleButton.java @@ -3,6 +3,11 @@ import edu.rpi.legup.model.rules.Rule; import javax.swing.*; +/** + * The {@code RuleButton} class is a custom button that represents a rule in the user interface. It + * extends {@link JButton} and is designed to display a rule's name and icon. The button is + * initialized with a {@link Rule} object, which provides the name and icon for the button. + */ public class RuleButton extends JButton { private Rule rule; diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/RuleFrame.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/RuleFrame.java index 6279f93a4..b14091351 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/RuleFrame.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/RuleFrame.java @@ -10,6 +10,13 @@ import javax.swing.*; import javax.swing.border.TitledBorder; +/** + * The {@code RuleFrame} class is a panel that contains and manages multiple rule-related panels + * within a tabbed interface. It extends {@link JPanel} and organizes the display of various rule + * types such as direct rules, contradiction rules, and case rules. The frame uses a {@link + * JTabbedPane} to allow users to switch between different rule panels. It also includes a search + * bar panel and a status label for displaying additional information. + */ public class RuleFrame extends JPanel { private static final String checkBox = " \u2714 "; private static final String xBox = " \u2718 "; @@ -28,6 +35,12 @@ public class RuleFrame extends JPanel { private RuleController controller; + /** + * Constructs a new {@code RuleFrame} instance. Initializes the frame with tabs for the + * different rule panels, a search bar panel, and a status label. + * + * @param controller the {@link RuleController} instance that manages the rules for this frame + */ public RuleFrame(RuleController controller) { MaterialTabbedPaneUI tabOverride = @@ -118,7 +131,7 @@ public void resetSize() { /** * Set the status label to a value. Use resetStatus to clear it. * - * @param check true iff we want a check box, if false we'll have a red x box + * @param check true if we want a checkbox, if false we'll have a red x box * @param text the text we're setting the label to display */ public void setStatus(boolean check, String text) { @@ -156,22 +169,47 @@ public RuleController getController() { return controller; } + /** + * Gets the JTabbedPane used in this frame + * + * @return the JTabbedPane instance + */ public JTabbedPane getTabbedPane() { return tabbedPane; } + /** + * Gets the {@code DirectRulePanel} contained in this frame + * + * @return the {@link DirectRulePanel} instance + */ public DirectRulePanel getDirectRulePanel() { return DirectRulePanel; } + /** + * Gets the {@code CaseRulePanel} contained in this frame + * + * @return the {@link CaseRulePanel} instance + */ public CaseRulePanel getCasePanel() { return casePanel; } + /** + * Gets the {@code ContradictionRulePanel} contained in this frame + * + * @return the {@link ContradictionRulePanel} instance + */ public ContradictionRulePanel getContradictionPanel() { return contradictionPanel; } + /** + * Gets the {@code SearchBarPanel} contained in this frame + * + * @return the {@link SearchBarPanel} instance + */ public SearchBarPanel getSearchPanel() { return searchPanel; } diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/RulePanel.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/RulePanel.java index 5d985d5c2..85ddf82b5 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/RulePanel.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/RulePanel.java @@ -9,6 +9,10 @@ import java.util.List; import javax.swing.*; +/** + * Abstract base class for panels displaying rules. Each subclass will represent a specific type of + * rule panel (e.g., DirectRulePanel, CaseRulePanel). + */ public abstract class RulePanel extends JPanel { protected ImageIcon icon; protected String name; @@ -32,7 +36,7 @@ public RulePanel(RuleFrame ruleFrame) { } /** - * Gets the rule rule buttons + * Gets the array of rule buttons * * @return rule ruleButtons */ @@ -57,6 +61,15 @@ public void setRules(List rules) { ruleButtons[i] = new RuleButton(rule); ruleButtons[i].setPreferredSize( new Dimension(150, 150)); // adjust the size of each RuleButton + + if (rule.getRuleName().length() > 18) { + ruleButtons[i].setFont(new Font("Segoe UI", Font.PLAIN, 11)); + } + if (rule.getRuleName().length() > 20) { + ruleButtons[i].setFont(new Font("Segoe UI", Font.PLAIN, 10)); + } + System.out.println(ruleButtons[i].getFont().getName()); + ruleButtons[i].setHorizontalTextPosition(JButton.CENTER); ruleButtons[i].setVerticalTextPosition(JButton.BOTTOM); @@ -69,6 +82,7 @@ public void setRules(List rules) { revalidate(); } + /** Updates the rules displayed by reloading images and setting the rules again. */ public void updateRules() { for (Rule rule : rules) { rule.loadImage(); @@ -324,28 +338,58 @@ public List getRules() { return rules; } + /** + * Gets the icon associated with this panel + * + * @return The ImageIcon associated with this panel + */ public ImageIcon getIcon() { return icon; } + /** + * Sets the icon for this panel + * + * @return the ImageIcon associated with this panel + */ public void setIcon(ImageIcon icon) { this.icon = icon; } + /** + * Gets the name of this panel + * + * @return the name of this panel in a String + */ @Override public String getName() { return name; } + /** + * Sets the name of this panel + * + * @param name the name to set for this panel + */ @Override public void setName(String name) { this.name = name; } + /** + * Gets the tooltip text associated with this panel + * + * @return the tooltip text of this panel + */ public String getToolTip() { return toolTip; } + /** + * Sets the tooltip text for this panel + * + * @param toolTip the tooltip text to set for this panel + */ public void setToolTip(String toolTip) { this.toolTip = toolTip; } diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/SearchBarPanel.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/SearchBarPanel.java index aba4707cd..60e24b986 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/SearchBarPanel.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/rulesview/SearchBarPanel.java @@ -2,6 +2,11 @@ import javax.swing.*; +/** + * The {@code SearchBarPanel} class creates a panel that allows users to search for rules within the + * rule frame. This panel provides a search bar for entering rule names and finding corresponding + * rules. + */ public class SearchBarPanel extends RulePanel { /** * SearchBarPanel Constructor creates a SearchBarPanel diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeElementView.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeElementView.java index 33c04717d..e5e523e11 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeElementView.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeElementView.java @@ -4,6 +4,11 @@ import edu.rpi.legup.model.tree.TreeElementType; import java.awt.*; +/** + * Abstract base class for views of tree elements in the tree structure. This class implements the + * Shape interface to support custom drawing and interaction with tree elements. It holds properties + * for rendering, interaction, and layout of the tree elements. + */ public abstract class TreeElementView implements Shape { protected TreeElement treeElement; protected double span; @@ -36,7 +41,7 @@ protected TreeElementView(TreeElementType type, TreeElement treeElement) { public abstract void draw(Graphics2D graphics2D); /** - * Gets the span for the sub tree rooted at this view + * Gets the span for the subtree rooted at this view * * @return span bounded y span */ @@ -45,7 +50,7 @@ public double getSpan() { } /** - * Sets the span for the sub tree rooted at this view. + * Sets the span for the subtree rooted at this view. * * @param span bounded y span */ diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeNodeView.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeNodeView.java index 990d96620..c9aedaef3 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeNodeView.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeNodeView.java @@ -7,6 +7,12 @@ import java.awt.geom.*; import java.util.ArrayList; +/** + * Represents a view of a tree node in the tree structure. This class extends {@link + * TreeElementView} and provides specific rendering and interaction functionality for tree nodes. It + * includes visual properties and methods to manage the node's appearance, location, and its + * relationships with other nodes. + */ public class TreeNodeView extends TreeElementView { static final int RADIUS = 25; static final int DIAMETER = 2 * RADIUS; @@ -260,51 +266,130 @@ public int getRadius() { return RADIUS; } + /** + * Returns the bounding rectangle of this TreeNodeView + * + * @return a Rectangle representing the bounding box of this TreeNodeView + */ @Override public Rectangle getBounds() { return new Rectangle(location.x, location.y, DIAMETER, DIAMETER); } + /** + * Returns the bounding rectangle of this TreeNodeView as a Rectangle2D + * + * @return a Rectangle2D representing the bounding box of this TreeNodeView + */ @Override public Rectangle2D getBounds2D() { return new Rectangle(location.x, location.y, DIAMETER, DIAMETER); } + /** + * Determines if the specified point (x, y) is within the bounds of this TreeNodeView + * + * @param x the x-coordinate of the point to check + * @param y the y-coordinate of the point to check + * @return {@code true} if the point is within the bounds of this TreeNodeView; {@code false} + * otherwise + */ @Override public boolean contains(double x, double y) { return Math.sqrt(Math.pow(x - location.x, 2) + Math.pow(y - location.y, 2)) <= RADIUS; } + /** + * Determines if the specified Point2D object is within the bounds of this TreeNodeView + * + * @param p the Point2D object representing the point to check + * @return {@code true} if the point is within the bounds of this TreeNodeView; {@code false} + * otherwise + */ @Override public boolean contains(Point2D p) { return contains(p.getX(), p.getY()); } + /** + * Determines if the specified rectangle defined by (x, y, width, height) intersects with the + * bounds of this TreeNodeView. + * + * @param x The x-coordinate of the rectangle to check + * @param y The y-coordinate of the rectangle to check + * @param w The width of the rectangle to check + * @param h The height of the rectangle to check + * @return {@code true} if the rectangle intersects with the bounds of this TreeNodeView; {@code + * false} otherwise + */ @Override public boolean intersects(double x, double y, double w, double h) { return false; } + /** + * Determines if the specified Rectangle2D object intersects with the bounds of this + * TreeNodeView. + * + * @param r the Rectangle2D object representing the rectangle to check + * @return {@code true} if the rectangle intersects with the bounds of this TreeNodeView; {@code + * false} otherwise + */ @Override public boolean intersects(Rectangle2D r) { return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); } + /** + * Determines if the specified rectangle defined by (x, y, width, height) is entirely contained + * within the bounds of this TreeNodeView + * + * @param x the x-coordinate of the rectangle to check + * @param y the y-coordinate of the rectangle to check + * @param w the width of the rectangle to check + * @param h the height of the rectangle to check + * @return {@code true} if the rectangle is entirely contained within the bounds of this + * TreeNodeView; {@code false} otherwise + */ @Override public boolean contains(double x, double y, double w, double h) { return false; } + /** + * Determines if the specified Rectangle2D object is entirely contained within the bounds of + * this TreeNodeView. + * + * @param r the Rectangle2D object representing the rectangle to check + * @return {@code true} if the rectangle is entirely contained within the bounds of this + * TreeNodeView; {@code false} otherwise + */ @Override public boolean contains(Rectangle2D r) { return false; } + /** + * Returns an iterator over the path geometry of this TreeNodeView. The iterator provides access + * to the path's segments and their coordinates, which can be used for rendering or hit testing. + * + * @param at the AffineTransform to apply to the path geometry + * @return a PathIterator that iterates over the path geometry of this TreeNodeView + */ @Override public PathIterator getPathIterator(AffineTransform at) { return null; } + /** + * Returns an iterator over the path geometry of this TreeNodeView with the specified flatness. + * The iterator provides access to the path's segments and their coordinates, which can be used + * for rendering or hit testing. + * + * @param at the AffineTransform to apply to the path geometry + * @param flatness the maximum distance that the line segments can deviate from the true path + * @return a PathIterator that iterates over the path geometry of this TreeNodeView + */ @Override public PathIterator getPathIterator(AffineTransform at, double flatness) { return null; diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreePanel.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreePanel.java index b6a29f2b5..936b0de95 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreePanel.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreePanel.java @@ -17,6 +17,11 @@ import javax.swing.JPanel; import javax.swing.border.TitledBorder; +/** + * {@code TreePanel} is a JPanel that manages and displays a tree view with associated toolbar and + * status information. It provides methods to interact with the tree view, such as adding, deleting, + * and merging tree elements, and updating the status based on actions performed. + */ public class TreePanel extends JPanel { public boolean modifiedSinceSave = false; public boolean modifiedSinceUndoPush = false; @@ -29,6 +34,7 @@ public class TreePanel extends JPanel { private JLabel status; + /** Constructs a {@code TreePanel} and initializes the UI components. */ public TreePanel(/*LegupUI legupUI*/ ) { // this.legupUI = legupUI; @@ -60,10 +66,20 @@ public TreePanel(/*LegupUI legupUI*/ ) { updateStatusTimer = 0; } + /** + * Repaints the tree view with the provided {@link Tree} object + * + * @param tree the {@link Tree} object to update the view with + */ public void repaintTreeView(Tree tree) { treeView.updateTreeView(tree); } + /** + * Updates the status of the panel based on changes to the {@link Board} + * + * @param board the {@link Board} object representing the current board state + */ public void boardDataChanged(Board board) { modifiedSinceSave = true; modifiedSinceUndoPush = true; @@ -71,6 +87,10 @@ public void boardDataChanged(Board board) { // colorTransitions(); } + /** + * Updates the status display based on the status timer. If the timer is greater than 0, the + * status will not be updated. Otherwise, it clears the status text. + */ public void updateStatus() { updateStatusTimer = ((updateStatusTimer - 1) > 0) ? (updateStatusTimer - 1) : 0; if (updateStatusTimer > 0) { @@ -79,22 +99,41 @@ public void updateStatus() { this.status.setText(""); } + /** + * Updates the status display with the given status string + * + * @param statusString the status string to display + */ public void updateStatus(String statusString) { status.setForeground(Color.BLACK); status.setFont(MaterialFonts.REGULAR); status.setText(statusString); } + /** + * Updates the status display as an error with an error message + * + * @param error the error message to display + */ public void updateError(String error) { status.setForeground(Color.RED); status.setFont(MaterialFonts.ITALIC); status.setText(error); } + /** + * Gets the {@link TreeView} instance associated with this panel + * + * @return the {@link TreeView} instance + */ public TreeView getTreeView() { return treeView; } + /** + * Adds a new tree element by executing an {@link AddTreeElementCommand}. If the command cannot + * be executed, it updates the status display with an error and error message. + */ public void add() { TreeViewSelection selection = treeView.getSelection(); @@ -107,6 +146,10 @@ public void add() { } } + /** + * Deletes the selected tree element by executing a {@link DeleteTreeElementCommand}. If the + * command cannot be executed, it updates the status display with an error and an error message. + */ public void delete() { TreeViewSelection selection = treeView.getSelection(); @@ -119,6 +162,10 @@ public void delete() { } } + /** + * Merges selected tree elements by executing a {@link MergeCommand}. If the command cannot be + * executed, it updates the status display with an error and an error message. + */ public void merge() { TreeViewSelection selection = treeView.getSelection(); @@ -131,6 +178,10 @@ public void merge() { } } + /** + * Toggles the collapsed state of the selected tree elements. If an element is collapsed, it + * will be expanded, and vice versa. + */ public void collapse() { TreeViewSelection selection = treeView.getSelection(); for (TreeElementView view : selection.getSelectedViews()) { diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeToolBarButton.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeToolBarButton.java index 002092155..232498723 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeToolBarButton.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeToolBarButton.java @@ -3,11 +3,18 @@ import java.awt.Dimension; import javax.swing.*; +/** {@code TreeToolBarButton} is a JButton that represents a button in the tree toolbar. */ public class TreeToolBarButton extends JButton { private TreeToolBarName name; private final Dimension MINIMUM_DIMENSION = new Dimension(60, 60); + /** + * Constructs a {@code TreeToolBarButton} with the specified icon and name. + * + * @param imageIcon the {@link ImageIcon} to be displayed on the button + * @param name the {@link TreeToolBarName} associated with this button + */ public TreeToolBarButton(ImageIcon imageIcon, TreeToolBarName name) { super(imageIcon); this.name = name; @@ -16,6 +23,11 @@ public TreeToolBarButton(ImageIcon imageIcon, TreeToolBarName name) { this.setFocusPainted(false); } + /** + * Gets the {@link TreeToolBarName} associated with this button + * + * @return the {@link TreeToolBarName} associated with this button + */ public TreeToolBarName getToolBarName() { return name; } diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeToolBarName.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeToolBarName.java index c805021be..3aec664be 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeToolBarName.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeToolBarName.java @@ -1,5 +1,9 @@ package edu.rpi.legup.ui.proofeditorui.treeview; +/** + * {@code TreeToolBarName} defines the names of actions represented by buttons in the tree toolbar. + * These actions are used for managing tree elements within the UI. + */ public enum TreeToolBarName { ADD_CHILD, DEL_CHILD, diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeToolbarPanel.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeToolbarPanel.java index 8f3ebfc23..06af84c57 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeToolbarPanel.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeToolbarPanel.java @@ -3,6 +3,10 @@ import java.awt.*; import javax.swing.*; +/** + * {@code TreeToolbarPanel} is a JPanel that provides a toolbar for managing tree elements in the + * tree view. It includes buttons for adding, deleting, merging, and collapsing nodes. + */ public class TreeToolbarPanel extends JPanel { private TreePanel treePanel; private TreeToolBarButton addChild, delChild, merge, collapse; diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeTransitionView.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeTransitionView.java index b022ac596..5a3aa7d5c 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeTransitionView.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeTransitionView.java @@ -10,6 +10,11 @@ import java.util.ArrayList; import java.util.List; +/** + * {@code TreeTransitionView} is a visual representation of a tree transition in the tree view. It + * extends TreeElementView and displays a transition arrow between tree nodes and handles various + * visual states such as selection, hover, and correctness. + */ public class TreeTransitionView extends TreeElementView { static final int RADIUS = 25; static final int DIAMETER = 2 * RADIUS; @@ -266,87 +271,177 @@ public void removeParentView(TreeNodeView nodeView) { } } - public Point getEndPoint() { - return endPoint; - } - - public void setEndPoint(Point endPoint) { - this.endPoint = endPoint; - } - + /** + * Gets the x-coordinate of the end point of the transition arrow + * + * @return the x-coordinate of the end point + */ public int getEndX() { return endPoint.x; } + /** + * Sets the x-coordinate of the end point of the transition arrow + * + * @param x the new x-coordinate of the end point + */ public void setEndX(int x) { this.endPoint.x = x; } + /** + * Gets the y-coordinate of the end point of the transition arrow + * + * @return the y-coordinate of the end point + */ public int getEndY() { return endPoint.y; } + /** + * Sets the y-coordinate of the end point of the transition arrow + * + * @param y the new y-coordinate of the end point + */ public void setEndY(int y) { this.endPoint.y = y; } - public List getLineStartPoints() { - return lineStartPoints; - } - - public void setLineStartPoints(List lineStartPoints) { - this.lineStartPoints = lineStartPoints; - } - + /** + * Gets the start point at the specified index from the list of start points + * + * @param index the index of the start point to retrieve + * @return the start point at the specified index, or null if the index is out of range + */ public Point getLineStartPoint(int index) { return index < lineStartPoints.size() ? lineStartPoints.get(index) : null; } + /** + * Returns the bounding rectangle of this TreeTransitionView + * + * @return a Rectangle representing the bounding box of this TreeTransitionView + */ @Override public Rectangle getBounds() { return arrowhead.getBounds(); } + /** + * Returns the bounding rectangle of this TreeTransitionView as a Rectangle2D + * + * @return a Rectangle2D representing the bounding box of this TreeTransitionView + */ @Override public Rectangle2D getBounds2D() { return arrowhead.getBounds2D(); } + /** + * Determines if the specified point (x, y) is within the bounds of this TreeTransitionView + * + * @param x the x-coordinate of the point to check + * @param y the y-coordinate of the point to check + * @return {@code true} if the point is within the bounds of this TreeTransitionView; {@code + * false} otherwise + */ @Override public boolean contains(double x, double y) { return arrowhead.contains(x, y); } + /** + * Determines if the specified Point2D object is within the bounds of this TreeTransitionView + * + * @param p the Point2D object representing the point to check + * @return {@code true} if the point is within the bounds of this TreeTransitionView; {@code + * false} otherwise + */ @Override public boolean contains(Point2D p) { return arrowhead != null && arrowhead.contains(p); } + /** + * Determines if the specified rectangle defined by (x, y, width, height) intersects with the + * bounds of this TreeTransitionView. + * + * @param x The x-coordinate of the rectangle to check + * @param y The y-coordinate of the rectangle to check + * @param w The width of the rectangle to check + * @param h The height of the rectangle to check + * @return {@code true} if the rectangle intersects with the bounds of this TreeTransitionView; + * {@code false} otherwise + */ @Override public boolean intersects(double x, double y, double w, double h) { return arrowhead.intersects(x, y, w, h); } + /** + * Determines if the specified Rectangle2D object intersects with the bounds of this + * TreeTransitionView. + * + * @param r the Rectangle2D object representing the rectangle to check + * @return {@code true} if the rectangle intersects with the bounds of this TreeTransitionView; + * {@code false} otherwise + */ @Override public boolean intersects(Rectangle2D r) { return arrowhead.intersects(r); } + /** + * Determines if the specified rectangle defined by (x, y, width, height) is entirely contained + * within the bounds of this TreeTransitionView + * + * @param x the x-coordinate of the rectangle to check + * @param y the y-coordinate of the rectangle to check + * @param w the width of the rectangle to check + * @param h the height of the rectangle to check + * @return {@code true} if the rectangle is entirely contained within the bounds of this + * TreeTransitionView; {@code false} otherwise + */ @Override public boolean contains(double x, double y, double w, double h) { return arrowhead.contains(x, y, w, h); } + /** + * Determines if the specified Rectangle2D object is entirely contained within the bounds of + * this TreeTransitionView. + * + * @param r the Rectangle2D object representing the rectangle to check + * @return {@code true} if the rectangle is entirely contained within the bounds of this + * TreeTransitionView; {@code false} otherwise + */ @Override public boolean contains(Rectangle2D r) { return arrowhead.contains(r); } + /** + * Returns an iterator over the path geometry of this TreeTransitionView. The iterator provides + * access to the path's segments and their coordinates, which can be used for rendering or hit + * testing. + * + * @param at the AffineTransform to apply to the path geometry + * @return a PathIterator that iterates over the path geometry of this TreeTransitionView + */ @Override public PathIterator getPathIterator(AffineTransform at) { return arrowhead.getPathIterator(at); } + /** + * Returns an iterator over the path geometry of this TreeTransitionView with the specified + * flatness. The iterator provides access to the path's segments and their coordinates, which + * can be used for rendering or hit testing. + * + * @param at the AffineTransform to apply to the path geometry + * @param flatness the maximum distance that the line segments can deviate from the true path + * @return a PathIterator that iterates over the path geometry of this TreeTransitionView + */ @Override public PathIterator getPathIterator(AffineTransform at, double flatness) { return arrowhead.getPathIterator(at, flatness); diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeView.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeView.java index f491009b4..65f49ff0a 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeView.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeView.java @@ -26,6 +26,12 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +/** + * The {@code TreeView} class provides a graphical representation of a {@code Tree} structure, + * allowing interaction and visualization of tree elements, transitions, and selections. It extends + * {@code ScrollView} and implements {@code ITreeListener} to respond to updates in the tree + * structure. + */ public class TreeView extends ScrollView implements ITreeListener { private static final Logger LOGGER = LogManager.getLogger(TreeView.class.getName()); @@ -51,6 +57,11 @@ public class TreeView extends ScrollView implements ITreeListener { private TreeViewSelection selection; + /** + * Constructs a {@code TreeView} with the specified {@code TreeController}. + * + * @param treeController the {@code TreeController} used to manage tree operations + */ public TreeView(TreeController treeController) { super(treeController); currentStateBoxes = new ArrayList<>(); @@ -62,6 +73,11 @@ public TreeView(TreeController treeController) { selection = new TreeViewSelection(); } + /** + * Gets the current tree view selection + * + * @return the {@code TreeViewSelection} object representing the current selection + */ public TreeViewSelection getSelection() { return selection; } @@ -131,6 +147,11 @@ private TreeElementView getTreeElementView(Point point, TreeElementView elementV return null; } + /** + * Updates the tree view with the specified {@code Tree} + * + * @param tree the {@code Tree} to display in the view + */ public void updateTreeView(Tree tree) { this.tree = tree; if (selection.getSelectedViews().size() == 0) { @@ -148,6 +169,7 @@ public void setTree(Tree tree) { this.tree = tree; } + /** Updates the size of the tree view based on the bounds of its tree */ public void updateTreeSize() { if (GameBoardFacade.getInstance().getTree() == null) { return; @@ -155,15 +177,21 @@ public void updateTreeSize() { setSize(bounds.getSize()); } + /** Resets the view if the tree bounds have been modified */ public void reset() { if (bounds.x != 0 || bounds.y != 0) { updateTreeSize(); } } + /** + * Adjusts the zoom level to fit the entire tree within the viewport when the Resize Proof + * button is selected + */ public void zoomFit() { - double fitWidth = (viewport.getWidth() - 8.0) / (getSize().width - 200); - double fitHeight = (viewport.getHeight() - 8.0) / (getSize().height - 120); + final int MIN_HEIGHT = 200; + double fitWidth = (viewport.getWidth() - 7.0) / (getSize().width - 75); + double fitHeight = (viewport.getHeight()) / Math.max((getSize().height - 115), MIN_HEIGHT); zoomTo(Math.min(fitWidth, fitHeight)); viewport.setViewPosition(new Point(0, viewport.getHeight() / 2)); } @@ -212,6 +240,11 @@ public void layoutContainer(Container parent) { }; } + /** + * Draws the tree view on the provided {@code Graphics2D} context + * + * @param graphics2D the {@code Graphics2D} context to draw on + */ public void draw(Graphics2D graphics2D) { currentStateBoxes.clear(); Tree tree = GameBoardFacade.getInstance().getTree(); @@ -234,11 +267,21 @@ public void draw(Graphics2D graphics2D) { } } + /** + * Resets the zoom level to its default state and positions the viewport from the top-left + * corner + */ public void zoomReset() { zoomTo(1.0); viewport.setViewPosition(new Point(0, 0)); } + /** + * Recursively redraws the tree starting from the specified node view + * + * @param graphics2D the {@code Graphics2D} context to draw on + * @param nodeView the {@code TreeNodeView} to start drawing from + */ private void redrawTree(Graphics2D graphics2D, TreeNodeView nodeView) { if (nodeView != null) { nodeView.draw(graphics2D); @@ -249,6 +292,11 @@ private void redrawTree(Graphics2D graphics2D, TreeNodeView nodeView) { } } + /** + * Removes the specified {@code TreeElementView} from the tree view + * + * @param view the {@code TreeElementView} to remove + */ public void removeTreeElement(TreeElementView view) { if (view.getType() == NODE) { TreeNodeView nodeView = (TreeNodeView) view; @@ -282,6 +330,7 @@ public void drawMouseOver(Graphics2D g) { } } + /** Resets the view by clearing the current tree, root node view, and selection */ public void resetView() { this.tree = null; this.rootNodeView = null; @@ -407,8 +456,17 @@ public TreeElementView getElementView(TreeElement element) { return viewMap.get(element); } - private void removeTreeNode(TreeNode node) { + /** + * Removes the specified {@link TreeNode} and its associated views + * + * @param node the {@link TreeNode} to be removed + */ + public void removeTreeNode(TreeNode node) { viewMap.remove(node); + if (node.getChildren() != null) { + node.getChildren().forEach(t -> removeTreeTransition(t)); + } + List children = node.getChildren(); // if child is a case rule, unlock ancestor elements @@ -435,11 +493,13 @@ private void removeTreeNode(TreeNode node) { } // set modifiable if started modifiable - boolean modifiable = - tree.getRootNode() - .getBoard() - .getPuzzleElement(oldElement) - .isModifiable(); + boolean modifiable = false; + if (tree != null) { + tree.getRootNode() + .getBoard() + .getPuzzleElement(oldElement) + .isModifiable(); + } // unmodifiable if already modified TreeNode modNode = ancestor.getParent().getParents().get(0); @@ -457,22 +517,60 @@ private void removeTreeNode(TreeNode node) { } } } - node.getChildren().forEach(t -> removeTreeTransition(t)); } - private void removeTreeTransition(TreeTransition trans) { + /** + * Removes the specified {@link TreeTransition} and its associated views + * + * @param trans the {@link TreeTransition} to be removed + */ + public void removeTreeTransition(TreeTransition trans) { viewMap.remove(trans); if (trans.getChildNode() != null) { removeTreeNode(trans.getChildNode()); } + + // Update transition modifiability if removing a case rule + List parents = trans.getParents(); + for (TreeNode parent : parents) { + // if transition is a case rule, unlock ancestor elements up until latest case rule or + // root node + boolean nextAncestorIsCaseRule = false; + Rule rule = trans.getRule(); + if (rule instanceof CaseRule) { + List ancestors = parent.getAncestors(); + for (int i = 0; i < ancestors.size(); i++) { + if (ancestors.get(i).getParent() == null) { + continue; + } + if (nextAncestorIsCaseRule) { + break; + } + for (PuzzleElement element : parent.getBoard().getPuzzleElements()) { + PuzzleElement curElement = + ancestors.get(i).getParent().getBoard().getPuzzleElement(element); + if (!curElement.isModifiableCaseRule()) { + curElement.setModifiableCaseRule(true); + } + } + if (ancestors.get(i).getParent().getRule() instanceof CaseRule) { + nextAncestorIsCaseRule = true; + } + } + } + } } + /** + * Adds the specified {@link TreeNode} and its associated views + * + * @param node the {@link TreeNode} to be added + */ private void addTreeNode(TreeNode node) { TreeTransition parent = node.getParent(); - TreeNodeView nodeView = new TreeNodeView(node); - TreeTransitionView parentView = (TreeTransitionView) viewMap.get(parent); + TreeTransitionView parentView = (TreeTransitionView) viewMap.get(parent); nodeView.setParentView(parentView); parentView.setChildView(nodeView); @@ -507,47 +605,51 @@ private void addTreeNode(TreeNode node) { } } + /** + * Adds the specified {@link TreeTransition} and its associated views + * + * @param trans The {@link TreeTransition} to be added + */ private void addTreeTransition(TreeTransition trans) { List parents = trans.getParents(); - TreeTransitionView transView = new TreeTransitionView(trans); + for (TreeNode parent : parents) { TreeNodeView parentNodeView = (TreeNodeView) viewMap.get(parent); transView.addParentView(parentNodeView); parentNodeView.addChildrenView(transView); + viewMap.put(trans, transView); + // if transition is a new case rule, lock dependent ancestor elements Rule rule = trans.getRule(); if (rule instanceof CaseRule && parent.getChildren().size() == 1) { - CaseRule caseRule = (CaseRule) rule; - List ancestors = parent.getAncestors(); for (TreeNode ancestor : ancestors) { // for all ancestors but root if (ancestor.getParent() == null) { continue; } - for (PuzzleElement element : - caseRule.dependentElements(parent.getBoard(), trans.getSelection())) { - // increment and lock - PuzzleElement oldElement = + + for (PuzzleElement element : parent.getBoard().getPuzzleElements()) { + PuzzleElement curElement = ancestor.getParent().getBoard().getPuzzleElement(element); - oldElement.setCasesDepended(oldElement.getCasesDepended() + 1); - oldElement.setModifiable(false); + curElement.setModifiableCaseRule(false); } } } } - viewMap.put(trans, transView); - if (trans.getChildNode() != null) { addTreeNode(trans.getChildNode()); } } - /// New Draw Methods - + /** + * Draws the tree using the provided {@link Graphics2D} object + * + * @param graphics2D the {@link Graphics2D} object used for drawing the tree + */ public void drawTree(Graphics2D graphics2D) { if (tree == null) { LOGGER.error("Unable to draw tree."); @@ -573,6 +675,11 @@ public void drawTree(Graphics2D graphics2D) { } } + /** + * Creates views for the given {@link TreeNodeView} and its children + * + * @param nodeView the {@link TreeNodeView} for which to create views + */ public void createViews(TreeNodeView nodeView) { if (nodeView != null) { viewMap.put(nodeView.getTreeElement(), nodeView); @@ -606,6 +713,14 @@ public void createViews(TreeNodeView nodeView) { } } + /** + * Calculates the layout locations (x and y coordinates) of the nodes in the tree. This method + * recursively traverses the tree and updates the positions of nodes and transitions based on + * their depth and parent relationships. + * + * @param nodeView the node view to calculate the positions for + * @param depth the depth of the node in the tree, used to calculate its x-coordinate + */ public void calculateViewLocations(TreeNodeView nodeView, int depth) { nodeView.setDepth(depth); int xLoc = (NODE_GAP_WIDTH + DIAMETER) * depth + DIAMETER; @@ -711,6 +826,13 @@ public void calculateViewLocations(TreeNodeView nodeView, int depth) { } } + /** + * Calculates the span (height) required for the given view, including its children. This method + * recursively determines the span for nodes and transitions based on their children and the + * merging branches they belong to. + * + * @param view the view whose span is to be calculated + */ public void calcSpan(TreeElementView view) { if (view.getType() == NODE) { TreeNodeView nodeView = (TreeNodeView) view; @@ -779,12 +901,12 @@ public void calcSpan(TreeElementView view) { } /** - * Calculates the sub span of a given sub tree rooted at the specified view and stops at the - * tree puzzleElement view specified as stop. Stop tree puzzleElement is NOT included in the - * span calculation + * Calculates the span of a subtree rooted at the specified view, stopping at the given stop + * view. The stop view is not included in the span calculation. * - * @param view - * @param stop + * @param view the root view of the subtree to calculate the span for + * @param stop the view at which to stop the span calculation. The stop view itself is not + * included in the span calculation */ private void subCalcSpan(TreeElementView view, TreeElementView stop) { // safe-guard for infinite loop @@ -859,12 +981,14 @@ private void subCalcSpan(TreeElementView view, TreeElementView stop) { } /** - * Reorders branches such that merging branches are sequentially grouped together and - * transitions are kept in relative order in the list of child transitions of the specified node + * Reorders the branches of a given node such that branches that merge are grouped together + * sequentially. Transitions are kept in their relative order based on their original positions + * in the list of child transitions of the specified node. This ensures that the visual + * representation of the branches and transitions maintains a logical and readable structure. * - * @param node root node of the branches - * @param branches DisjointSets of the child branches of the specified node which determine - * which branches merge + * @param node the root node whose branches are to be reordered + * @param branches a DisjointSets structure representing the merging relationships of the child + * branches of the specified node. This determines which branches should be grouped together */ private void reorderBranches(TreeNode node, DisjointSets branches) { List children = node.getChildren(); diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeViewSelection.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeViewSelection.java index 71a65b49e..c509b6cb9 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeViewSelection.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeViewSelection.java @@ -4,6 +4,11 @@ import java.util.ArrayList; import java.util.List; +/** + * {@code TreeViewSelection} manages the selection and hover state of tree element views in a tree + * view. It maintains a list of selected views, tracks the currently hovered view, and manages the + * mouse position. + */ public class TreeViewSelection { private ArrayList selectedViews; private TreeElementView hover; diff --git a/src/main/java/edu/rpi/legup/ui/puzzleeditorui/elementsview/ElementFrame.java b/src/main/java/edu/rpi/legup/ui/puzzleeditorui/elementsview/ElementFrame.java index e0524f84d..1619ecbaa 100644 --- a/src/main/java/edu/rpi/legup/ui/puzzleeditorui/elementsview/ElementFrame.java +++ b/src/main/java/edu/rpi/legup/ui/puzzleeditorui/elementsview/ElementFrame.java @@ -2,7 +2,6 @@ import edu.rpi.legup.controller.EditorElementController; import edu.rpi.legup.model.Puzzle; -import edu.rpi.legup.ui.lookandfeel.components.MaterialTabbedPaneUI; import java.awt.*; import javax.swing.*; import javax.swing.border.TitledBorder; @@ -14,52 +13,36 @@ public class ElementFrame extends JPanel { private static final String htmlTail = ""; private PlaceableElementPanel placeableElementPanel; - private NonPlaceableElementPanel nonPlaceableElementPanel; - private JTabbedPane tabbedPane; + // private JTabbedPane tabbedPane; private ButtonGroup buttonGroup; private EditorElementController controller; public ElementFrame(EditorElementController controller) { + this.controller = controller; - MaterialTabbedPaneUI tabOverride = - new MaterialTabbedPaneUI() { - // this prevents the tabs from moving around when you select them - @Override - protected boolean shouldRotateTabRuns(int i) { - return false; - } - }; - - this.tabbedPane = new JTabbedPane(); - tabbedPane.setUI(tabOverride); + JLabel status = new JLabel("", SwingConstants.CENTER); this.buttonGroup = new ButtonGroup(); - nonPlaceableElementPanel = new NonPlaceableElementPanel(this); - // nonPlaceableElementPanel.setMinimumSize(new Dimension(100,200)); - tabbedPane.addTab( - nonPlaceableElementPanel.getName(), - nonPlaceableElementPanel.getIcon(), - new JScrollPane(nonPlaceableElementPanel), - nonPlaceableElementPanel.getToolTip()); + // Parent panel to hold all elements + JPanel elementPanel = new JPanel(); + elementPanel.setLayout(new BoxLayout(elementPanel, BoxLayout.Y_AXIS)); placeableElementPanel = new PlaceableElementPanel(this); - // placeableElementPanel.setMinimuSize(new Dimension(100,200)); - tabbedPane.addTab( - placeableElementPanel.getName(), - placeableElementPanel.getIcon(), - new JScrollPane(placeableElementPanel), - placeableElementPanel.getToolTip()); - tabbedPane.setTabPlacement(JTabbedPane.TOP); + placeableElementPanel.setMinimumSize(new Dimension(100, 200)); + elementPanel.add(new JScrollPane(placeableElementPanel)); + // Set layout and dimensions for the main panel setLayout(new BorderLayout()); setMinimumSize(new Dimension(250, 256)); setPreferredSize(new Dimension(330, 256)); - add(tabbedPane); + // Add components to the main panel + add(elementPanel, BorderLayout.CENTER); add(status, BorderLayout.SOUTH); + // Center-align the titled border TitledBorder title = BorderFactory.createTitledBorder("Elements"); title.setTitleJustification(TitledBorder.CENTER); setBorder(title); @@ -69,29 +52,26 @@ public ButtonGroup getButtonGroup() { return buttonGroup; } - public void resetSize() { - int buttonWidth = - ((ElementPanel) tabbedPane.getSelectedComponent()) - .getElementButtons()[0].getWidth(); - this.setMinimumSize(new Dimension(2 * buttonWidth + 64, this.getHeight())); - } + // public void resetSize() { + // int buttonWidth = + // ((ElementPanel) tabbedPane.getSelectedComponent()) + // .getElementButtons()[0].getWidth(); + // this.setMinimumSize(new Dimension(2 * buttonWidth + 64, this.getHeight())); + // } public void setElements(Puzzle puzzle) { - nonPlaceableElementPanel.setElements(puzzle.getNonPlaceableElements()); - placeableElementPanel.setElements(puzzle.getPlaceableElements()); + if (puzzle != null) { + placeableElementPanel.setElements(puzzle.getPlaceableElements()); + } } public EditorElementController getController() { return controller; } - public JTabbedPane getTabbedPane() { - return tabbedPane; - } - - public NonPlaceableElementPanel getNonPlaceableElementPanel() { - return nonPlaceableElementPanel; - } + // public JTabbedPane getTabbedPane() { + // return tabbedPane; + // } public PlaceableElementPanel getPlaceableElementPanel() { return placeableElementPanel; diff --git a/src/main/java/edu/rpi/legup/ui/puzzleeditorui/elementsview/ElementPanel.java b/src/main/java/edu/rpi/legup/ui/puzzleeditorui/elementsview/ElementPanel.java index 46198e226..70826d25c 100644 --- a/src/main/java/edu/rpi/legup/ui/puzzleeditorui/elementsview/ElementPanel.java +++ b/src/main/java/edu/rpi/legup/ui/puzzleeditorui/elementsview/ElementPanel.java @@ -20,7 +20,7 @@ public ElementPanel(ElementFrame eFrame) { setLayout(new WrapLayout()); } - public void setElements(List elements) { + public int setElements(List elements) { this.elements = elements; clearButtons(); @@ -38,6 +38,7 @@ public void setElements(List elements) { add(elementButtons[i]); } revalidate(); + return elements.size(); } protected void clearButtons() { diff --git a/src/main/java/edu/rpi/legup/ui/puzzleeditorui/elementsview/NonPlaceableElementPanel.java b/src/main/java/edu/rpi/legup/ui/puzzleeditorui/elementsview/NonPlaceableElementPanel.java index 00b4f5379..91e8d2e49 100644 --- a/src/main/java/edu/rpi/legup/ui/puzzleeditorui/elementsview/NonPlaceableElementPanel.java +++ b/src/main/java/edu/rpi/legup/ui/puzzleeditorui/elementsview/NonPlaceableElementPanel.java @@ -1,15 +1,15 @@ -package edu.rpi.legup.ui.puzzleeditorui.elementsview; - -import javax.swing.*; - -public class NonPlaceableElementPanel extends ElementPanel { - public NonPlaceableElementPanel(ElementFrame elementFrame) { - super(elementFrame); - this.icon = - new ImageIcon( - ClassLoader.getSystemClassLoader() - .getResource("edu/rpi/legup/images/Legup/Direct Rules.gif")); - this.name = "Non-Placeable Elements"; - this.toolTip = "Non-Placeable Elements"; - } -} +// package edu.rpi.legup.ui.puzzleeditorui.elementsview; +// +// import javax.swing.*; +// +// public class NonPlaceableElementPanel extends ElementPanel { +// public NonPlaceableElementPanel(ElementFrame elementFrame) { +// super(elementFrame); +// this.icon = +// new ImageIcon( +// ClassLoader.getSystemClassLoader() +// .getResource("edu/rpi/legup/images/Legup/Direct Rules.gif")); +// this.name = "Non-Placeable Elements"; +// this.toolTip = "Non-Placeable Elements"; +// } +// } diff --git a/src/main/resources/edu/rpi/legup/images/Legup/Reset.png b/src/main/resources/edu/rpi/legup/images/Legup/Reset.png new file mode 100644 index 000000000..fd6ffefa0 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/Legup/Reset.png differ diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/CompleteRowColumnDirectRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/CompleteRowColumnDirectRule.png index a74654d43..27ccec972 100644 Binary files a/src/main/resources/edu/rpi/legup/images/binary/rules/CompleteRowColumnDirectRule.png and b/src/main/resources/edu/rpi/legup/images/binary/rules/CompleteRowColumnDirectRule.png differ diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/DuplicateRowOrColumnContradictionRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/DuplicateRowOrColumnContradictionRule.png deleted file mode 100644 index 214aa5348..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/binary/rules/DuplicateRowOrColumnContradictionRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/OneOrZeroCaseRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/OneOrZeroCaseRule.png deleted file mode 100644 index 73072f2ce..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/binary/rules/OneOrZeroCaseRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/OneTileGapDirectRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/OneTileGapDirectRule.png deleted file mode 100644 index b68f67e44..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/binary/rules/OneTileGapDirectRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/PreventTrioDirectRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/PreventTrioDirectRule.png new file mode 100644 index 000000000..ed0701f26 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/binary/rules/PreventTrioDirectRule.png differ diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/RepeatedRowColumnContradictionRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/RepeatedRowColumnContradictionRule.png new file mode 100644 index 000000000..5cc030a76 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/binary/rules/RepeatedRowColumnContradictionRule.png differ diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/SaveBlockerDirectRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/SaveBlockerDirectRule.png new file mode 100644 index 000000000..7695ae2da Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/binary/rules/SaveBlockerDirectRule.png differ diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/SurroundPairDirectRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/SurroundPairDirectRule.png deleted file mode 100644 index 67a4e47f3..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/binary/rules/SurroundPairDirectRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/ThreeAdjacentContradictionRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/ThreeAdjacentContradictionRule.png deleted file mode 100644 index 862408b63..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/binary/rules/ThreeAdjacentContradictionRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/TrioContradictionRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/TrioContradictionRule.png new file mode 100644 index 000000000..e292ec9fa Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/binary/rules/TrioContradictionRule.png differ diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/UnbalancedRowColumnContradictionRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/UnbalancedRowColumnContradictionRule.png index 029bd12ac..bd66c16a7 100644 Binary files a/src/main/resources/edu/rpi/legup/images/binary/rules/UnbalancedRowColumnContradictionRule.png and b/src/main/resources/edu/rpi/legup/images/binary/rules/UnbalancedRowColumnContradictionRule.png differ diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/UniqueRowColumnDirectRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/UniqueRowColumnDirectRule.png new file mode 100644 index 000000000..7785d198c Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/binary/rules/UniqueRowColumnDirectRule.png differ diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/WastedBlockerContradictionRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/WastedBlockerContradictionRule.png new file mode 100644 index 000000000..8813d7956 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/binary/rules/WastedBlockerContradictionRule.png differ diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/ZeroOrOneCaseRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/ZeroOrOneCaseRule.png new file mode 100644 index 000000000..7394bff91 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/binary/rules/ZeroOrOneCaseRule.png differ diff --git a/src/main/resources/edu/rpi/legup/images/binary/tiles/NumberTile.png b/src/main/resources/edu/rpi/legup/images/binary/tiles/NumberTile.png new file mode 100644 index 000000000..41fb61dfa Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/binary/tiles/NumberTile.png differ diff --git a/src/main/resources/edu/rpi/legup/images/binary/tiles/UnknownTile.png b/src/main/resources/edu/rpi/legup/images/binary/tiles/UnknownTile.png new file mode 100644 index 000000000..9ab9f7481 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/binary/tiles/UnknownTile.png differ diff --git a/src/main/resources/edu/rpi/legup/images/skyscrapers/cases/CellForNumber.png b/src/main/resources/edu/rpi/legup/images/skyscrapers/cases/CellForNumber.png index 7ab0cdbef..e8cca03b4 100644 Binary files a/src/main/resources/edu/rpi/legup/images/skyscrapers/cases/CellForNumber.png and b/src/main/resources/edu/rpi/legup/images/skyscrapers/cases/CellForNumber.png differ diff --git a/src/main/resources/edu/rpi/legup/images/skyscrapers/cases/NumberForCell.png b/src/main/resources/edu/rpi/legup/images/skyscrapers/cases/NumberForCell.png index fedbbbac8..307d0d754 100644 Binary files a/src/main/resources/edu/rpi/legup/images/skyscrapers/cases/NumberForCell.png and b/src/main/resources/edu/rpi/legup/images/skyscrapers/cases/NumberForCell.png differ diff --git a/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/DuplicateNumber.png b/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/DuplicateNumber.png index 7ba489c2b..6ab3cd769 100644 Binary files a/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/DuplicateNumber.png and b/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/DuplicateNumber.png differ diff --git a/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/ExceedingVisibility.png b/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/ExceedingVisibility.png index ff999f4bc..2f5b5237d 100644 Binary files a/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/ExceedingVisibility.png and b/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/ExceedingVisibility.png differ diff --git a/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/InsufficientVisibility.png b/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/InsufficientVisibility.png index cfe04a503..285451ba1 100644 Binary files a/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/InsufficientVisibility.png and b/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/InsufficientVisibility.png differ diff --git a/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/PreemptiveVisibility.png b/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/PreemptiveVisibility.png index 6d330baa6..6c4a6e457 100644 Binary files a/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/PreemptiveVisibility.png and b/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/PreemptiveVisibility.png differ diff --git a/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/UnresolvedCell.png b/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/UnresolvedCell.png index 76947671d..ae78caf49 100644 Binary files a/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/UnresolvedCell.png and b/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/UnresolvedCell.png differ diff --git a/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/UnresolvedNumber.png b/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/UnresolvedNumber.png index 8862702f1..602f62702 100644 Binary files a/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/UnresolvedNumber.png and b/src/main/resources/edu/rpi/legup/images/skyscrapers/contradictions/UnresolvedNumber.png differ diff --git a/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/FixedMax.png b/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/FixedMax.png index 8a26bbfeb..86a4ee8a0 100644 Binary files a/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/FixedMax.png and b/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/FixedMax.png differ diff --git a/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/LastCell.png b/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/LastCell.png index 216a0f3a1..8db8a3bbf 100644 Binary files a/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/LastCell.png and b/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/LastCell.png differ diff --git a/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/LastNumber.png b/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/LastNumber.png index 1e0555648..dcf4cb2b8 100644 Binary files a/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/LastNumber.png and b/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/LastNumber.png differ diff --git a/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/NEdge.png b/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/NEdge.png index d3dd36bbc..b238b36aa 100644 Binary files a/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/NEdge.png and b/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/NEdge.png differ diff --git a/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/OneEdge.png b/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/OneEdge.png index 40d6ff65f..6841a8034 100644 Binary files a/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/OneEdge.png and b/src/main/resources/edu/rpi/legup/images/skyscrapers/rules/OneEdge.png differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/black.gif b/src/main/resources/edu/rpi/legup/images/starbattle/black.gif deleted file mode 100644 index 13381a717..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/black.gif and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/cases/StarOrEmptyCaseRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/cases/StarOrEmptyCaseRule.png deleted file mode 100644 index 0383711bb..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/cases/StarOrEmptyCaseRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/ClashingOrbitContradictionRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/ClashingOrbitContradictionRule.png deleted file mode 100644 index d25340b40..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/ClashingOrbitContradictionRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/TooFewStarsContradictionRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/TooFewStarsContradictionRule.png deleted file mode 100644 index cc019752f..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/TooFewStarsContradictionRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/TooManyStarsContradictionRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/TooManyStarsContradictionRule.png deleted file mode 100644 index b468ae6f6..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/TooManyStarsContradictionRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/empty.gif b/src/main/resources/edu/rpi/legup/images/starbattle/empty.gif deleted file mode 100644 index 38b91d0a2..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/empty.gif and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/BlackOutDirectRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/BlackOutDirectRule.png deleted file mode 100644 index f72fd7d7f..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/rules/BlackOutDirectRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/ColumnsWithinRegionsDirectRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/ColumnsWithinRegionsDirectRule.png deleted file mode 100644 index 1a88e9d07..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/rules/ColumnsWithinRegionsDirectRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/ColumnsWithinRowsDirectRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/ColumnsWithinRowsDirectRule.png deleted file mode 100644 index 24fb0d48a..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/rules/ColumnsWithinRowsDirectRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/FinishWithStarDirectRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/FinishWithStarDirectRule.png deleted file mode 100644 index 11cfbf899..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/rules/FinishWithStarDirectRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/RegionsWithinColumnsDirectRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/RegionsWithinColumnsDirectRule.png deleted file mode 100644 index fde683dcd..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/rules/RegionsWithinColumnsDirectRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/RegionsWithinRowsDirectRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/RegionsWithinRowsDirectRule.png deleted file mode 100644 index 30170df40..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/rules/RegionsWithinRowsDirectRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/RowsWithinColumnsDirectRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/RowsWithinColumnsDirectRule.png deleted file mode 100644 index bac64a87c..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/rules/RowsWithinColumnsDirectRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/RowsWithinRegionsDirectRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/RowsWithinRegionsDirectRule.png deleted file mode 100644 index 8907e0475..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/rules/RowsWithinRegionsDirectRule.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/SurroundStar.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/SurroundStar.png deleted file mode 100644 index 13287f779..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/rules/SurroundStar.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/star.gif b/src/main/resources/edu/rpi/legup/images/starbattle/star.gif deleted file mode 100644 index e289b287a..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/star.gif and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/sudoku/AdvancedDeduction.png b/src/main/resources/edu/rpi/legup/images/sudoku/AdvancedDeduction.png deleted file mode 100644 index d51538baf..000000000 Binary files a/src/main/resources/edu/rpi/legup/images/sudoku/AdvancedDeduction.png and /dev/null differ diff --git a/src/main/resources/edu/rpi/legup/images/sudoku/rules/NoCellForNumberColumn.png b/src/main/resources/edu/rpi/legup/images/sudoku/rules/NoCellForNumberColumn.png new file mode 100644 index 000000000..9dcade64b Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/sudoku/rules/NoCellForNumberColumn.png differ diff --git a/src/main/resources/edu/rpi/legup/images/sudoku/rules/NoCellForNumberRegion.png b/src/main/resources/edu/rpi/legup/images/sudoku/rules/NoCellForNumberRegion.png new file mode 100644 index 000000000..000dc8b68 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/sudoku/rules/NoCellForNumberRegion.png differ diff --git a/src/main/resources/edu/rpi/legup/images/sudoku/rules/NoCellForNumberRow.png b/src/main/resources/edu/rpi/legup/images/sudoku/rules/NoCellForNumberRow.png new file mode 100644 index 000000000..f984f8365 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/sudoku/rules/NoCellForNumberRow.png differ diff --git a/src/main/resources/edu/rpi/legup/images/sudoku/NoSolution.png b/src/main/resources/edu/rpi/legup/images/sudoku/rules/NoSolution.png similarity index 100% rename from src/main/resources/edu/rpi/legup/images/sudoku/NoSolution.png rename to src/main/resources/edu/rpi/legup/images/sudoku/rules/NoSolution.png diff --git a/src/main/resources/edu/rpi/legup/images/sudoku/PossibleValues.png b/src/main/resources/edu/rpi/legup/images/sudoku/rules/PossibleValues.png similarity index 100% rename from src/main/resources/edu/rpi/legup/images/sudoku/PossibleValues.png rename to src/main/resources/edu/rpi/legup/images/sudoku/rules/PossibleValues.png diff --git a/src/main/resources/edu/rpi/legup/images/sudoku/RepeatedNumber.png b/src/main/resources/edu/rpi/legup/images/sudoku/rules/RepeatedNumber.png similarity index 100% rename from src/main/resources/edu/rpi/legup/images/sudoku/RepeatedNumber.png rename to src/main/resources/edu/rpi/legup/images/sudoku/rules/RepeatedNumber.png diff --git a/src/main/resources/edu/rpi/legup/images/sudoku/forcedByDeduction.png b/src/main/resources/edu/rpi/legup/images/sudoku/rules/forcedByDeduction.png similarity index 100% rename from src/main/resources/edu/rpi/legup/images/sudoku/forcedByDeduction.png rename to src/main/resources/edu/rpi/legup/images/sudoku/rules/forcedByDeduction.png diff --git a/src/main/resources/edu/rpi/legup/images/sudoku/forcedByElimination.png b/src/main/resources/edu/rpi/legup/images/sudoku/rules/forcedByElimination.png similarity index 100% rename from src/main/resources/edu/rpi/legup/images/sudoku/forcedByElimination.png rename to src/main/resources/edu/rpi/legup/images/sudoku/rules/forcedByElimination.png diff --git a/src/main/resources/edu/rpi/legup/images/sudoku/possible_cells_number.png b/src/main/resources/edu/rpi/legup/images/sudoku/rules/possible_cells_number.png similarity index 100% rename from src/main/resources/edu/rpi/legup/images/sudoku/possible_cells_number.png rename to src/main/resources/edu/rpi/legup/images/sudoku/rules/possible_cells_number.png diff --git a/src/main/resources/edu/rpi/legup/images/sudoku/rules/possible_cells_number_column.png b/src/main/resources/edu/rpi/legup/images/sudoku/rules/possible_cells_number_column.png new file mode 100644 index 000000000..2ebdc5823 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/sudoku/rules/possible_cells_number_column.png differ diff --git a/src/main/resources/edu/rpi/legup/images/sudoku/rules/possible_cells_number_region.png b/src/main/resources/edu/rpi/legup/images/sudoku/rules/possible_cells_number_region.png new file mode 100644 index 000000000..cc371e3e9 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/sudoku/rules/possible_cells_number_region.png differ diff --git a/src/main/resources/edu/rpi/legup/images/sudoku/rules/possible_cells_number_row.png b/src/main/resources/edu/rpi/legup/images/sudoku/rules/possible_cells_number_row.png new file mode 100644 index 000000000..80476a428 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/sudoku/rules/possible_cells_number_row.png differ diff --git a/src/main/resources/edu/rpi/legup/images/sudoku/tem.png b/src/main/resources/edu/rpi/legup/images/sudoku/rules/tem.png similarity index 100% rename from src/main/resources/edu/rpi/legup/images/sudoku/tem.png rename to src/main/resources/edu/rpi/legup/images/sudoku/rules/tem.png diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/white.gif b/src/main/resources/edu/rpi/legup/images/sudoku/tiles/NumberTile.png similarity index 82% rename from src/main/resources/edu/rpi/legup/images/starbattle/white.gif rename to src/main/resources/edu/rpi/legup/images/sudoku/tiles/NumberTile.png index fc2c683eb..5a8540d02 100644 Binary files a/src/main/resources/edu/rpi/legup/images/starbattle/white.gif and b/src/main/resources/edu/rpi/legup/images/sudoku/tiles/NumberTile.png differ diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/UnknownTile.png b/src/main/resources/edu/rpi/legup/images/sudoku/tiles/UnknownTile.png similarity index 100% rename from src/main/resources/edu/rpi/legup/images/starbattle/UnknownTile.png rename to src/main/resources/edu/rpi/legup/images/sudoku/tiles/UnknownTile.png diff --git a/src/main/resources/edu/rpi/legup/legup/config b/src/main/resources/edu/rpi/legup/legup/config index 1ee9ed79c..e01767677 100644 --- a/src/main/resources/edu/rpi/legup/legup/config +++ b/src/main/resources/edu/rpi/legup/legup/config @@ -34,26 +34,22 @@ fileCreationDisabled="false"/> - + fileCreationDisabled="false"/> - - - + + + diff --git a/src/test/java/legup/TestUtilities.java b/src/test/java/legup/TestUtilities.java index 83ce773d4..cd9f4a935 100644 --- a/src/test/java/legup/TestUtilities.java +++ b/src/test/java/legup/TestUtilities.java @@ -6,11 +6,19 @@ import edu.rpi.legup.model.tree.TreeNode; import edu.rpi.legup.model.tree.TreeTransition; import edu.rpi.legup.save.InvalidFileFormatException; +import java.io.InputStream; public final class TestUtilities { public static void importTestBoard(String fileName, Puzzle puzzle) throws InvalidFileFormatException { - puzzle.importPuzzle(ClassLoader.getSystemResourceAsStream(fileName)); + InputStream inputStream = ClassLoader.getSystemResourceAsStream(fileName); + + if (inputStream == null) { + throw new IllegalArgumentException( + "InputStream cannot be null. File not found: " + fileName); + } + + puzzle.importPuzzle(inputStream); Tree tree = puzzle.getTree(); TreeNode rootNode = tree.getRootNode(); Board board = rootNode.getBoard().copy(); diff --git a/src/test/java/puzzles/nurikabe/rules/FinishRoomCaseRuleTest.java b/src/test/java/puzzles/nurikabe/rules/FinishRoomCaseRuleTest.java index 5f5b6d35a..32a440d13 100644 --- a/src/test/java/puzzles/nurikabe/rules/FinishRoomCaseRuleTest.java +++ b/src/test/java/puzzles/nurikabe/rules/FinishRoomCaseRuleTest.java @@ -27,10 +27,7 @@ public static void setUp() { nurikabe = new Nurikabe(); } - /** - * Tests the Finish Room case rule by ensuring that it results in 5 or less children, that - * contain a modified cell that is white - */ + /** Tests the Finish Room case rule by ensuring it produces the correct number of children */ @Test public void FinishRoomCaseRule_FinishRoomCaseRuleBaseTest() throws InvalidFileFormatException { TestUtilities.importTestBoard( @@ -81,34 +78,6 @@ public void FinishRoomCaseRule_FinishRoomCaseRuleBaseTest() throws InvalidFileFo NurikabeCell cell2 = board.getCell(4, 2); ArrayList cases2 = RULE.getCases(board, cell2); - Assert.assertEquals(6, cases2.size()); // correctly stops generating possible cases after - // more than 5 (the max) is found. Would have generated 8 cases - // FinishRoomCaseRule finny = new FinishRoomCaseRule(); - // finny.checkRuleRaw(); - // "Invalid use of the case rule FinishRoom: This case rule must have 5 or less children." - - // getErrorString in auto case rule - // should display "The selection can produce a max of 5 cases." - // AutoCaseRuleCommand autoCaseRuleCommand = new AutoCaseRuleCommand(elementView, selection, - // caseBoard.getCaseRule(), caseBoard, e); - - // NurikabeBoard caseyBoard = (NurikabeBoard) cases2.get(0); - // NurikabeBoard caseyBoard2 = (NurikabeBoard) cases2.get(1); - // NurikabeBoard caseyBoard3 = (NurikabeBoard) cases2.get(2); - // NurikabeBoard caseyBoard4 = (NurikabeBoard) cases2.get(3); - // NurikabeBoard caseyBoard5 = (NurikabeBoard) cases2.get(4); - // NurikabeBoard caseyBoard6 = (NurikabeBoard) cases2.get(5); - // NurikabeBoard caseyBoard7 = (NurikabeBoard) cases2.get(6); - // NurikabeBoard caseyBoard8 = (NurikabeBoard) cases2.get(7); - // - // NurikabeType boardy1Type = caseyBoard.getCell(5,5).getType(); - // NurikabeType boardy2Type = caseyBoard2.getCell(6,6).getType(); - // NurikabeType boardy3Type = caseyBoard.getCell(5,5).getType(); - // NurikabeType boardy4Type = caseyBoard2.getCell(6,6).getType(); - // NurikabeType boardy5Type = caseyBoard.getCell(5,5).getType(); - // NurikabeType boardy6Type = caseyBoard2.getCell(6,6).getType(); - // NurikabeType boardy7Type = caseyBoard.getCell(5,5).getType(); - // NurikabeType boardy8Type = caseyBoard2.getCell(6,6).getType(); - + Assert.assertEquals(9, cases2.size()); } } diff --git a/src/test/java/puzzles/sudoku/rules/LastNumberForCellDirectRuleRegionTest.java b/src/test/java/puzzles/sudoku/rules/LastNumberForCellDirectRuleRegionTest.java new file mode 100644 index 000000000..f27f38d8a --- /dev/null +++ b/src/test/java/puzzles/sudoku/rules/LastNumberForCellDirectRuleRegionTest.java @@ -0,0 +1,100 @@ +package puzzles.sudoku.rules; + +import edu.rpi.legup.model.tree.TreeNode; +import edu.rpi.legup.model.tree.TreeTransition; +import edu.rpi.legup.puzzle.sudoku.Sudoku; +import edu.rpi.legup.puzzle.sudoku.SudokuBoard; +import edu.rpi.legup.puzzle.sudoku.SudokuCell; +import edu.rpi.legup.puzzle.sudoku.rules.LastNumberForCellDirectRule; +import edu.rpi.legup.save.InvalidFileFormatException; +import legup.MockGameBoardFacade; +import legup.TestUtilities; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class LastNumberForCellDirectRuleRegionTest { + private static final LastNumberForCellDirectRule RULE = new LastNumberForCellDirectRule(); + private static Sudoku sudoku; + + @BeforeClass + public static void setUp() { + MockGameBoardFacade.getInstance(); + sudoku = new Sudoku(); + } + + @Test + public void LastNumberForCellDirectRule_FullRegionTest() throws InvalidFileFormatException { + // Import board and create transition + TestUtilities.importTestBoard( + "puzzles/sudoku/rules/LastNumberForCellDirectRule/FullRegion", sudoku); + TreeNode rootNode = sudoku.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + // Loop all numbers at point + for (int i = 1; i < 10; i++) { + // Reset board + SudokuBoard board = (SudokuBoard) transition.getBoard(); + // Set cell + SudokuCell cell1 = board.getCell(2, 5); + cell1.setData(i); + board.addModifiedData(cell1); + + // Test the case + if (i == 9) { + Assert.assertNull(RULE.checkRuleAt(transition, cell1)); + } else { + Assert.assertNotNull(RULE.checkRuleAt(transition, cell1)); + } + } + + // Import Board and create transition + TestUtilities.importTestBoard( + "puzzles/sudoku/rules/LastNumberForCellDirectRule/FullRow", sudoku); + rootNode = sudoku.getTree().getRootNode(); + transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + // Loop all numbers at point + for (int i = 1; i < 10; i++) { + // Reset board + SudokuBoard board = (SudokuBoard) transition.getBoard(); + // Set cell + SudokuCell cell = board.getCell(4, 4); + cell.setData(i); + board.addModifiedData(cell); + + // Test the case + if (i == 5) { + Assert.assertNull(RULE.checkRuleAt(transition, cell)); + } else { + Assert.assertNotNull(RULE.checkRuleAt(transition, cell)); + } + } + + // Import Board and create transition + TestUtilities.importTestBoard( + "puzzles/sudoku/rules/LastNumberForCellDirectRule/FullMixed", sudoku); + rootNode = sudoku.getTree().getRootNode(); + transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + // Loop all numbers at point + for (int i = 1; i < 10; i++) { + // Reset board + SudokuBoard board = (SudokuBoard) transition.getBoard(); + // Set cell + SudokuCell cell = board.getCell(5, 3); + cell.setData(i); + board.addModifiedData(cell); + + // Test the case + if (i == 2) { + Assert.assertNull(RULE.checkRuleAt(transition, cell)); + } else { + Assert.assertNotNull(RULE.checkRuleAt(transition, cell)); + } + } + } +} diff --git a/src/test/java/puzzles/sudoku/rules/RepeatedNumberContradictionRuleTest.java b/src/test/java/puzzles/sudoku/rules/RepeatedNumberContradictionRuleTest.java new file mode 100644 index 000000000..704167a29 --- /dev/null +++ b/src/test/java/puzzles/sudoku/rules/RepeatedNumberContradictionRuleTest.java @@ -0,0 +1,88 @@ +package puzzles.sudoku.rules; + +import edu.rpi.legup.model.tree.TreeNode; +import edu.rpi.legup.model.tree.TreeTransition; +import edu.rpi.legup.puzzle.sudoku.Sudoku; +import edu.rpi.legup.puzzle.sudoku.SudokuBoard; +import edu.rpi.legup.puzzle.sudoku.SudokuCell; +import edu.rpi.legup.puzzle.sudoku.rules.RepeatedNumberContradictionRule; +import edu.rpi.legup.save.InvalidFileFormatException; +import legup.MockGameBoardFacade; +import legup.TestUtilities; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class RepeatedNumberContradictionRuleTest { + private static final RepeatedNumberContradictionRule RULE = + new RepeatedNumberContradictionRule(); + private static Sudoku sudoku; + + @BeforeClass + public static void setUp() { + MockGameBoardFacade.getInstance(); + sudoku = new Sudoku(); + } + + @Test + public void RepeatedNumberContradictionRule_GlobalTest() throws InvalidFileFormatException { + // Import board and create transition + TestUtilities.importTestBoard( + "puzzles/sudoku/rules/RepeatedNumberContradictionRule/BlankBoard7", sudoku); + TreeNode rootNode = sudoku.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + // Loop through every cell + for (int i = 0; i < 81; i++) { + // Reset board + SudokuBoard board = (SudokuBoard) transition.getBoard(); + // Set cell + int x = i / 9; + int y = i % 9; + if (x == 0 && y == 0) { + continue; + } + SudokuCell cell = board.getCell(x, y); + cell.setData(7); + + // Test the case + if (x == 0 || y == 0 || (x < 3 && y < 3)) { + Assert.assertNull(RULE.checkRuleAt(transition, cell)); + } else { + Assert.assertNotNull(RULE.checkRuleAt(transition, cell)); + } + cell.setData(0); + } + // Import board and create transition + TestUtilities.importTestBoard( + "puzzles/sudoku/rules/RepeatedNumberContradictionRule/BlankBoard4", sudoku); + rootNode = sudoku.getTree().getRootNode(); + transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + // Loop through every cell + for (int i = 0; i < 81; i++) { + // Reset board + SudokuBoard board = (SudokuBoard) transition.getBoard(); + // Set cell + int x = i / 9; + int y = i % 9; + if ((x == 3 && y == 0) || (x == 6 && y == 8)) { + continue; + } + SudokuCell cell = board.getCell(x, y); + cell.setData(4); + + // Test the case + if ((x == 3 || y == 0 || x == 6 || y == 8) + || (x > 2 && x < 6 && y < 3) + || (x > 5 && y > 5)) { + Assert.assertNull(RULE.checkRuleAt(transition, cell)); + } else { + Assert.assertNotNull(RULE.checkRuleAt(transition, cell)); + } + cell.setData(0); + } + } +} diff --git a/src/test/resources/puzzles/binary/rules/SurroundPairDirectRule/SurroundTwoZerosWithTwoOnes b/src/test/resources/puzzles/binary/rules/SurroundPairDirectRule/SurroundTwoZerosWithTwoOnes new file mode 100644 index 000000000..026742fea --- /dev/null +++ b/src/test/resources/puzzles/binary/rules/SurroundPairDirectRule/SurroundTwoZerosWithTwoOnes @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/binary/rules/SurroundPairDirectRule/test b/src/test/resources/puzzles/binary/rules/SurroundPairDirectRule/test new file mode 100644 index 000000000..e69de29bb diff --git a/src/test/resources/puzzles/nurikabe/rules/BlackBetweenRegionsDirectRule/SurroundTwoZerosWithTwoOnes b/src/test/resources/puzzles/nurikabe/rules/BlackBetweenRegionsDirectRule/SurroundTwoZerosWithTwoOnes new file mode 100644 index 000000000..026742fea --- /dev/null +++ b/src/test/resources/puzzles/nurikabe/rules/BlackBetweenRegionsDirectRule/SurroundTwoZerosWithTwoOnes @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/ColumnBlackout b/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/ColumnBlackout deleted file mode 100644 index ddcc4dc9a..000000000 --- a/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/ColumnBlackout +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/RegionBlackout b/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/RegionBlackout deleted file mode 100644 index f2a5b42d9..000000000 --- a/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/RegionBlackout +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/RowBlackout b/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/RowBlackout deleted file mode 100644 index f2a5b42d9..000000000 --- a/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/RowBlackout +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/resources/puzzles/sudoku/rules/LastCellForNumberDirectRule/CorneredRegion b/src/test/resources/puzzles/sudoku/rules/LastCellForNumberDirectRule/CorneredRegion new file mode 100644 index 000000000..4d3c57225 --- /dev/null +++ b/src/test/resources/puzzles/sudoku/rules/LastCellForNumberDirectRule/CorneredRegion @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/sudoku/rules/LastNumberForCellDirectRule/FullMixed b/src/test/resources/puzzles/sudoku/rules/LastNumberForCellDirectRule/FullMixed new file mode 100644 index 000000000..55b501fec --- /dev/null +++ b/src/test/resources/puzzles/sudoku/rules/LastNumberForCellDirectRule/FullMixed @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/sudoku/rules/LastNumberForCellDirectRule/FullRegion b/src/test/resources/puzzles/sudoku/rules/LastNumberForCellDirectRule/FullRegion new file mode 100644 index 000000000..58fd02162 --- /dev/null +++ b/src/test/resources/puzzles/sudoku/rules/LastNumberForCellDirectRule/FullRegion @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/sudoku/rules/LastNumberForCellDirectRule/FullRow b/src/test/resources/puzzles/sudoku/rules/LastNumberForCellDirectRule/FullRow new file mode 100644 index 000000000..07e502ed9 --- /dev/null +++ b/src/test/resources/puzzles/sudoku/rules/LastNumberForCellDirectRule/FullRow @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/sudoku/rules/RepeatedNumberContradictionRule/BlankBoard4 b/src/test/resources/puzzles/sudoku/rules/RepeatedNumberContradictionRule/BlankBoard4 new file mode 100644 index 000000000..abaa0ba6b --- /dev/null +++ b/src/test/resources/puzzles/sudoku/rules/RepeatedNumberContradictionRule/BlankBoard4 @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/sudoku/rules/RepeatedNumberContradictionRule/BlankBoard7 b/src/test/resources/puzzles/sudoku/rules/RepeatedNumberContradictionRule/BlankBoard7 new file mode 100644 index 000000000..5692dea64 --- /dev/null +++ b/src/test/resources/puzzles/sudoku/rules/RepeatedNumberContradictionRule/BlankBoard7 @@ -0,0 +1,11 @@ + + + + + + + + + + +