# ASCII Bookshelves

You know those stackable shelves that are basically just wooden boxes that can be stacked together? We're going to simulate building some bookshelves out of those with some ASCII art.

Our books are all conveniently uniform in size, and all look like the following:

|X|
|X|
|X|


The bookshelves are individual boxes, always three characters high on the inside (enough to fit a book standing upright), composed of | characters on the left and right, - characters for the top and bottom, and wide enough to fit X books (where X is an input integer). For example, here's a bookshelf of size 3:

|---------|
|         |
|         |
|         |
|---------|


because you can fit 3 books into it like so

|---------|
||X||X||X||
||X||X||X||
||X||X||X||
|---------|


The input is going to be two strictly positive integers, X and Y, where X is the width of the shelves we have (measured in books), and Y is how many books we have to stack. If we have more books than fit on a single shelf, we need to add more shelves to the top. For example, here is input 4 wide / 6 books:

|------------|
||X||X|      |
||X||X|      |
||X||X|      |
|------------|
|------------|
||X||X||X||X||
||X||X||X||X||
||X||X||X||X||
|------------|


If Y % X > 0, meaning the number of books is not an integer multiple of the shelf size, the remainder books should go on the top-most left-most position (as in the case with 4 6, above) and the remaining part of that shelf filled in with spaces.

### Input

• Two strictly positive integers in any convenient format, each >0.
• You can take the input in either order (e.g., size of shelves first, then number of books, or vice versa). Please state in your submission the input order.
• You can safely assume that neither input will be larger than your language's default [int] size (or equivalent).

### Output

The resulting ASCII art representation of the books and bookshelves.

### Rules

• Leading or trailing newlines or whitespace are all optional, so long as the characters themselves line up correctly.
• Either a full program or a function are acceptable. If a function, you can return the output rather than printing it.
• If possible, please include a link to an online testing environment so other people can try out your code!
• Standard loopholes are forbidden.
• This is so all usual golfing rules apply, and the shortest code (in bytes) wins.

### Further Examples

6 wide / 2 books
|------------------|
||X||X|            |
||X||X|            |
||X||X|            |
|------------------|

2 wide / 6 books
|------|
||X||X||
||X||X||
||X||X||
|------|
|------|
||X||X||
||X||X||
||X||X||
|------|
|------|
||X||X||
||X||X||
||X||X||
|------|

4 wide / 9 books
|------------|
||X|         |
||X|         |
||X|         |
|------------|
|------------|
||X||X||X||X||
||X||X||X||X||
||X||X||X||X||
|------------|
|------------|
||X||X||X||X||
||X||X||X||X||
||X||X||X||X||
|------------|

• Can I make it so the shelf with the least amount of books is on the bottom, so like it fills top to bottom – Golden Ratio Mar 3 '17 at 11:32
• @GoldenRatio No, the books must be filled bottom to top, left to right. – AdmBorkBork Mar 3 '17 at 13:18

## JavaScript (ES6), 10099 98 bytes

Takes the width w and the number of books b in currying syntax (w)(b).

w=>g=(b,s=|${'-'.repeat(w*3)}| ,r=s.replace(/---/g,_=>b&&b--?'|X|':' '))=>(b?g(b)+s:s)+r+r+r+s  ### Formatted and commented w => // main function: takes width 'w' as input, returns 'g' g = ( // g = recursive function with: b, // - b = number of books s = |${'-'.repeat(w * 3)}|\n, //   - s = top/bottom of shell, filled with '-'
r = s.replace(                  //   - r = pattern of the current row of books,
RegExp('---', 'g'),           //         using 's' as a template and updating
_ => b && b-- ? '|X|' : '   ' //         'b' while building it
)                               // NB: 'r' must be defined in the scope of 'g',
) =>                              //     otherwise it would be overwritten by
(                               //     subsequent calls
b ?                           // if there are remaining books:
g(b) + s                    //   do a recursive call and append shell top
:                             // else:
s                           //   just append shell top
) + r + r + r + s               // append book rows and shell bottom


### Test cases

let f =

w=>g=(b,s=|${'-'.repeat(w*3)}| ,r=s.replace(/---/g,_=>b&&b--?'|X|':' '))=>(b?g(b)+s:s)+r+r+r+s console.log(f(6)(2)) console.log(f(2)(6)) console.log(f(4)(9)) # Bash (+utilities), 130, 108, 106 bytes A single, continuous, shell pipeline to render your bookshelves. Changelog: • Optimized first sed expression a bit, -12 bytes (Thx @Riley !) • Replaced printf + seq with a raw printf, -10 bytes • Refactored the second sed expression, -2 bytes Golfed printf %$2s\\n|fold -$1|sed "s/ /|X|/g;:;/.\{$[$1*3]\}/!s/$/ /;t;h;s/./-/gp;x;p;p;p;x"|sed 's/.*/|&|/'|tac


$./shelf 6 8 |------------------| ||X||X| | ||X||X| | ||X||X| | |------------------| |------------------| ||X||X||X||X||X||X|| ||X||X||X||X||X||X|| ||X||X||X||X||X||X|| |------------------|  Try It Online ! How It Works $./shelf 2 3

printf %$2s\\n - generate n whitespace characters, one per book (shown as _) ___ fold -$1 - fold them by the shelf length

__
_


sed "s/ /|X|/g;" - replace _ with X, add book covers

|X||X|
|X|


:;/.\{$[$1*3]\}/!s/$/ /;t - right pad with spaces (shown as _) |X||X| |X|___  h;s/./-/gp;x;p;p;p;x - triplicate each line, and add --- before and after it. ------ |X||X| |X||X| |X||X| ------ ------ |X| |X| |X| ------  sed 's/.*/|&|/'|tac - wrap lines in | |, reverse with tac |------| ||X| | ||X| | ||X| | |------| |------| ||X||X|| ||X||X|| ||X||X|| |------|  • In the first sed you can use a nameless label, and t instead of b so you don't need the {}. You can skip the s/./-/g because they are already -s. Try it Online! – Riley Mar 2 '17 at 14:12 • @Riley That's an excellent advice, thank you ! – zeppelin Mar 2 '17 at 15:02 # Python 2, 133113 105 bytes I'm sure there's a better way... X,Y=input() k='|'+'---'*X+'|' while Y:g=Y%X or X;print k+'\n'+('|'+'|X|'*g+' '*(X-g)+'|'+'\n')*3+k;Y-=g  Input is taken width, books -20 bytes thanks to @ovs for noticing an unnecessary lambda function! -8 bytes thanks to @ovs for shortening the input. • X,Y=input() is a shorter way to take the to values. – ovs Mar 1 '17 at 22:24 • @ovs Oh wait, I put that there for my first try. Whoops. Nice catch, thanks! – HyperNeutrino Mar 2 '17 at 3:03 • @ovs Thanks, so then input is taken as X, Y, right? – HyperNeutrino Mar 2 '17 at 14:55 • I think that you can save two bytes by defining '|' as a variable. – Ørjan Johansen Mar 2 '17 at 18:09 ## Batch, 261 bytes @set/an=~-%1%%%2+1,b=%1-n @set s= @set t= @for /l %%i in (1,1,%2)do @call set t=---%%t%%&if %%i gtr %n% (call set s=%%s%% )else call set s=%%s%%X @for %%s in ("|%t%|" "|%s:X=|X|%|" "|%s:X=|X|%|" "|%s:X=|X|%|" "|%t%|")do @echo %%~s @if %b% gtr 0 %0 %b% %2  Uses my trick from my Batch answer to Let's play tennis to easily print lots of | characters. # Haskell, 100 bytes x#y returns the string for width x and y books. s?n=[1..n]>>s x#y|x<y=x#(y-x)++x#x|w<-"---"?x,b<-"|X|"?y++" "?(x-y)=[w,b,b,b,w]>>=('|':).(++"|\n")  Try it online! The main function/operator is #. When x<y it splits the books into y-x and x, then recurses. When x>=y, w and b are the two line types, minus the outer |s and the newline. The helper operator s?n concatenates n copies of the string s. # PowerShell, 149 134 bytes param($w,$b)$s="|$('-'*$w*3)|"
if($a=$b%$w){,$s+,"|$('|X|'*$a)$(' '*3*($w-$a))|"*3+$s}
if($b-=$a){(,$s+,"|$('|X|'*$w)|"*3+$s)*($b/$w)}


Try it online!

Takes input $width and $books. Sets string $s to be one of the horizontal shelves. Then we have two if statements. The first checks whether we have "remainder" books. If so, we output the shelf, the (number of books plus number of spaces)*3, and another shelf. Next, we see if we still have books remaining after removing the remainders ($a). Same sort of setup, except we're using $w number of books. Since at this point, $b is guaranteed to be a multiple of $w (because we removed the remainder, $a), we don't need to worry about rounding.

Removed the [math]::Floor() call, saving 15 bytes

All of these strings are left on the pipeline, and implicit Write-Output happens at program completion.

# CJam, 62 61 bytes

q~1a*W$/W$f{0e]}{{"|X|"S3*?}%s__'-3*W$*_}%1m>W%"| |"*"||"\*o;  Takes input as width books Try it online! Explanation q~ Read and eval input (pushes width W and books B to the stack) 1a* Push an array containing the number 1 B times W$/          Split it into chunks of size W
W$f{0e]} Pad each chunk to width W by adding 0's to the right (the last chunk might be shorter than W) { Apply the following to each chunk: { Apply the following to each number in the chunk: "|X|"S3*? Push "|X|" if the number is 1, or " " if it's 0 }% (end of block) s Stringify (joins with no separator) __ Duplicate twice (each shelf is 3 identical lines) '-3*W$*_     Push a string containing '-' repeated 3×W times, then duplicate it
}%           (end of block)
At this point we have an array containing sequences of 3 identical lines
each followed by two lines of -'s
1m>          Rotate the array 1 to the right; brings the final line of -'s to the start
W%           Reverse the array, so that the top shelf is the partially empty one
"|\n|"*      Join the array with the string "|\n|", to build the sides of the shelves
"||"\*       Join the string "||" with the shelf string (adds the first and last | chars)
o            Print the result


# Python 3, 142 bytes

Still working on it. b is for 'number of books' and w is for shelf width.

def s(b,w):
R=b%w
B='|\n'
I='|'
X='|X|'
d=I+3*w*'-'+B
f=I+X*w+B
p=I+R*X+3*(w-R)*' '+B
print(R and d+3*p+d or" ")+b//w*(d+3*f+d))

• Welcome to PPCG! This doesn't work for me unless R=b%w is moved down to the next line. Also, you should be able to remove the whitespace around those three = to save some bytes. – Business Cat Mar 2 '17 at 16:46
• Welcome to PPCG! – AdmBorkBork Mar 2 '17 at 16:57
• You can replace d+3*p+d if R!=0 else '' with R and d+3*p+d or'' – shooqie Mar 3 '17 at 14:52
• @shooqie I'm curious, how could this evaluate to the result of d+3*p+d? – Juan Meleiro Mar 4 '17 at 10:42
• You can save a few bytes by putting all the definitions in one line using semicolons. – L3viathan Apr 21 '17 at 19:13

# AHK, 208 bytes

AutoTrim,Off
w=%1%
b=%2%
f:=Mod(b,w)
Loop,%w%
s=%s%---
s=|%s%|n
If (f>0) {
Loop,%f%
t=%t%|X|
Loop,% w-f
t=%t%  
t=|%t%|n
t:=s t t t s
}
Loop,%w%
r=%r%|X|
r=|%r%|n
Loop,% (b-f)/w
t:=t s r r r s
Send,%t%


There are a few things frustating me from golfing further:

• AutoHotkey doesn't have a built-in repeat function
• You can't directly use the passed in arguments (%1% & %2%) in math functions because those expect variable or number input and it will assume the unescaped 1 to be the number one rather than the variable name
• I am not very good at golfing

An easier to read version of the above looks like this:

AutoTrim,Off
w=%1%
b=%2%
f:=Mod(b,w)

Loop,%w%
s=%s%---
s=|%s%|n

If (f>0) {
Loop,%f%
t=%t%|X|
Loop,% w-f
t=%t%  
t=|%t%|n
t:=s t t t s
}

Loop,%w%
r=%r%|X|
r=|%r%|n

Loop,% (b-f)/w
t:=t s r r r s

Send,%t%


If a Loop doesn't use brackets {}, then only the next line is part of the loop. If setting a variable's value using := instead of =, you can drop the percent sign escape characters. Tilde n is the newline character.

# Java 7, 230224 222 bytes

String c(int w,int b){String r="",n="|\n",z="|";int i=0,j,k,t=b%w<1?w:b%w,x=b/w+(t!=w?1:0);for(;i++<w;z+="---");z+=n;for(i=0;i<x;i++){r+=z;for(j=0;j++<3;r+=n){r+="|";for(k=0;k<w;r+=i<1&k++>=t?"   ":"|X|");}r+=z;}return r;}


Explanation:

String c(int w, int b){                // Method with two integer parameters and String return-type
String r = "",                       //  The return-String
n = "|\n",                    //  Part that's used multiple times in the code
z = "|";                      //  Shelf part of the book-boxes
int i = 0, j, k,                     //  Indexes used in the for-loops
t = b%w < 1 ? w : b%w,           //  Books on top shelf
x = b/w + (t != w ? 1 : 0);      //  Amount of shelves
for(; i++ < w; z += "---"); z += n;  //  Create the shelf-part ("|---|"; with w times "---")
for(i = 0; i < x; i++){              //  Loop over the rows
r += z;                            //   Append the result with the shelf-part
for(j = 0; j++ < 3; ){             //   Loop three times (the height of the books & boxes)
r += "|";                        //    Append the result-String with "|"
for(k = 0; k < w;                //    Loop over the columns
r +=                         //     And append the result-String with:
i < 1                       //      If this is the first row:
& k++ >= t ?                //      And the current column is larger or equal to the amount of books in the top shelf
"   "                     //       Use an empty space
:                           //      Else:
"|X|"                     //       Use the book-part
);                         //    End of columns loop
r += n;                       //    Append the result-String with a "|" and a new-line
}                               //   End of the loop of three
r += z;                          //   Append the result-String with the shelf-part
}                                  //  End of rows loop
return r;                          //  Return the result-String
}                                     // End of method


Test code:

Try it here.

class M{
static String c(int w,int b){String r="",n="|\n",z="|";int i=0,j,k,t=b%w<1?w:b%w,x=b/w+(t!=w?1:0);for(;i++<w;z+="---");z+=n;for(i=0;i<x;i++){r+=z;for(j=0;j++<3;r+=n){r+="|";for(k=0;k<w;r+=i<1&k++>=t?"   ":"|X|");}r+=z;}return r;}

public static void main(String[] a){
System.out.println(c(6, 2));
System.out.println(c(2, 6));
System.out.println(c(4, 9));
}
}


Output:

|------------------|
||X||X|            |
||X||X|            |
||X||X|            |
|------------------|

|------|
||X||X||
||X||X||
||X||X||
|------|
|------|
||X||X||
||X||X||
||X||X||
|------|
|------|
||X||X||
||X||X||
||X||X||
|------|

|------------|
||X|         |
||X|         |
||X|         |
|------------|
|------------|
||X||X||X||X||
||X||X||X||X||
||X||X||X||X||
|------------|
|------------|
||X||X||X||X||
||X||X||X||X||
||X||X||X||X||
|------------|

• I don't know how, but it looks like lambdas and .repeat really, really help in this challenge. – Olivier Grégoire Oct 25 '18 at 10:10
• @OlivierGrégoire Considering I posted this about about 1.5 year ago, I'm not surprised it can be golfed quite substantially. ;) – Kevin Cruijssen Oct 25 '18 at 10:18
• Ow... I hadn't checked the date: I only saw that this question was active and that a full other algorithm for Java was possible. My bad... – Olivier Grégoire Oct 25 '18 at 10:34
• @OlivierGrégoire No problem, and well done with your answer. :) It almost feels nostalgic looking back at this answer when I was still adding the test cases and output to the answer, and answered everything in Java 7, because I didn't understand Java 8 yet. XD – Kevin Cruijssen Oct 25 '18 at 10:52

# PowerShell, 109 bytes

param($w,$b)for(;$b;$b-=$c){if(!($c=$b%$w)){$c=$w}($l="|$('-'*$w*3)|") ,"|$('|X|'*$c)$(' '*($w-$c)*3)|"*3
$l}  Less golfed test script: $f = {

param($w,$b)
for(;$b;$b-=$c){ if(!($c=$b%$w)){$c=$w}
($l="|$('-'*$w*3)|") ,"|$('|X|'*$c)$(' '*($w-$c)*3)|"*3
$l } } @( ,(6, 2, "|------------------|", "||X||X| |", "||X||X| |", "||X||X| |", "|------------------|") ,(2, 6, "|------|", "||X||X||", "||X||X||", "||X||X||", "|------|", "|------|", "||X||X||", "||X||X||", "||X||X||", "|------|", "|------|", "||X||X||", "||X||X||", "||X||X||", "|------|") ,(4, 9, "|------------|", "||X| |", "||X| |", "||X| |", "|------------|", "|------------|", "||X||X||X||X||", "||X||X||X||X||", "||X||X||X||X||", "|------------|", "|------------|", "||X||X||X||X||", "||X||X||X||X||", "||X||X||X||X||", "|------------|") ) | % {$w,$b,$expected = $_$result = &$f$w $b "$result"-eq"$expected"$result
}


Output:

True
|------------------|
||X||X|            |
||X||X|            |
||X||X|            |
|------------------|
True
|------|
||X||X||
||X||X||
||X||X||
|------|
|------|
||X||X||
||X||X||
||X||X||
|------|
|------|
||X||X||
||X||X||
||X||X||
|------|
True
|------------|
||X|         |
||X|         |
||X|         |
|------------|
|------------|
||X||X||X||X||
||X||X||X||X||
||X||X||X||X||
|------------|
|------------|
||X||X||X||X||
||X||X||X||X||
||X||X||X||X||
|------------|


## PowerShell, 109 bytes, alternative

param($w,$b)for(;$b;$b-=$c){($l="|$('---'*$w)|")
,"|$('|X|'*($c=(($b%$w),$w-ne0)))$('   '*($w-$c))|"*3
$l}  # Python 2, 120 118 bytes i,j=input() a=j%i n='|\n' x='|'+'---'*i+n print(x+('|'+'|x|'*a+' '*(i-a)*3+n)*3,'')[a<1]+(x+('|'+'|x|'*i+n)*3)*(j/i)+x  Try it online! Have been meaning to have a go at this one for the last few days. Now that I have finally got time to do it there's already a shorter Python answer. Oh well, just posted as an alternative. Input taken as width,books # SOGL, 64 bytes be%→M"Q└ƨS‘*ač;┼→S%‘A |e3* -* |++M?tMSeM-9*@*a+┼Ot}be÷:?{teSa┼Ot  Explanation: First function:  →M define function M which pushes b the book amount % mod e the bookshelf width  second function:  →S create function S (example input: 3)  "Q└ƨS‘ push the string "|||XXX|||" (the book) [3, "|||XXX|||"] * multiply by the number on stack (book count) ["|||XXX||||||XXX||||||XXX|||"] a push variable A (later defined "|||") ["|||XXX||||||XXX||||||XXX|||", "|||"] č chop into char array ["|||XXX||||||XXX||||||XXX|||", ["|", "|", "|"]] ; swap top 2 on stack [["|", "|", "|"], "|||XXX||||||XXX||||||XXX|||"] ┼ horizontally append [["||X||X||X|", "||X||X||X|", "||X||X||X|"]]  this function expects a number (book count) on stack and outputs the bookshelfs books ["||X||X||X|", "||X||X||X|", "||X||X||X|"]  Further down example given is e=3 (bookshelf width) and b=8 (book amount) %‘A var A = "|||" | push "|" ["|"] e3* push E * 3 ["|", 9] -* push that many "-"es ["|", "---------"] |+ append "|" ["|", "---------|"] + prepend the "|" ["|---------|"]  this is the bookshelf top/bottom line and always stays on the stack first part (half-empty bookshelf) First main part M? } if the modulo != 0 tM output the bookshelf top/bottom line S execute the S function width the modulo eM- push bookshelf width - modulo (empty space count) 9* multiply by 9 (books are 3x3 so 3x3 spaces) @* get that many spaces a+ append to that "|||" ┼ horizontally append O output t output the bookshelf top/bottom line  And the last part be÷ floor divide book amout by width (full shelves) :? if not 0 (a bug makes all loops execute once) { repeat t output the bookshelf top/bottom line eS execute S with shelf width (full shelf) a┼ horizontally append "|||" O output t output the bookshelf top/bottom line  # Java (JDK), 124 bytes s->b->{for(int i=0;i++<(b-~s)/s*5;)System.out.printf("|%-"+s*3+"s|%n",i%5<2?"-".repeat(s*3):"|X|".repeat(i<5&b%s>0?b%s:s));}  Try it online! ## Credits # PHP>=7.1, 138 Bytes for([,$w,$b]=$argv;$i<ceil($b/$w)*5;)echo str_pad("|".str_repeat(["","|X|"][$t=($i+1)%5>1],$i++<5&&$b%$w?$b%$w:$w),$w*3+1,"- "[\$t])."|\n";


Online Version

# Canvas, 33 bytes

|X|３＊×⁷３×⇵-×|３＊×╫│；２２╋Ｐ
％？％⁸｝÷［⁷⁸


Try it here!

Explanation (some characters have been replaced to look more monospace):

|X|3*×⁷3×⇵-×|3*×╫│;22╋P  helper function. Prints a shelf with X books
|X|                      push "|X|"
3*                    repeat it 3 times vertically
×                   repeat that horizontally by the item (X) below on the stack
⁷3×                push width * 3
⇵               ceiling divide that by 2
-×             repeat "-" that many times
|3*          repeat "|" vertically 3 times (aka "|¶|¶|")
×         prepend that to the dashes (aka ¼ of a bookshelf)
╫│       quad-palindromize with horizontal overlap of the remainder
taken before and vertical overlap of 1
;      get the books on top
22╋   and at coordinates (2;2) in the shelf, place them in
P  print that whole thing

%?%⁸}÷[⁷⁸
%?  }      if width%amount (!= 0)
%⁸         execute the helper function with width%amount on the stack
÷[    repeat floor(width/amount) times
⁷     push width
⁸    execute the helper function


# Pip-n, 45 bytes

Wb-:yPPZ['-X3Xa"|X|"X(Yb%a|a).sX3Xa-yRL3]WR'|


Takes the width and book count, respectively, as command-line arguments. Try it online!

### Explanation

We run a loop to print the shelves one by one from top to bottom. At each iteration, we update b (the number of books to print) by subtracting y (the number of books printed on that iteration). When b reaches 0, the loop exits.

Wb-:yPPZ['-X3Xa"|X|"X(Yb%a|a).sX3Xa-yRL3]WR'|
a is 1st cmdline arg (shelf width); b is 2nd cmdline
arg (# books); s is space; y is ""
Note that "" becomes zero in numeric contexts
Wb-:y                                          While b decremented by y is nonzero:
b%a|a                    b mod a, or a if that quantity is zero
Y                         Yank that value into y
(      )                   This is the number of books on the current shelf
"|X|"                            Book-spine string
X                           Repeated y times
a-y           Number of empty slots on the current shelf
sX3X              Three spaces for each slot
.                  Concatenate to the book-spines string
RL3        Make a list of 3 copies of that string
'-X3Xa                                 3*a hyphens
[                               ]       Put that string and the above list in a list
WR'|   Wrap all strings in the nested list in |
PZ                                        Palindromize the outer list (adding a copy of the
hyphens to the end of it)
P                                          Print, joining all sublists on newlines (-n flag)


Because that's a little involved, here's an example of the first iteration when a = 3, b = 8:

Yb%a|a       2
"|X|"X ^     "|X||X|"
^ .sX3Xa-y   "|X||X|   "
^ RL3        ["|X||X|   ";"|X||X|   ";"|X||X|   "]
['-X3Xa ^ ]  ["---------";["|X||X|   ";"|X||X|   ";"|X||X|   "]]
^ WR'|       ["|---------|";["||X||X|   |";"||X||X|   |";"||X||X|   |"]]
PZ ^         ["|---------|";["||X||X|   |";"||X||X|   |";"||X||X|   |"];"|---------|"]


which then prints as

|---------|
||X||X|   |
||X||X|   |
||X||X|   |
|---------|


# Pyth, 56 bytes

M++GHGV_fTs*V,]Q1.DEQjCg*5\|smgL\-*L3?d"|X|""   ".[*]1N0


Accepts shelf width, book count as separate arguments in that order. Try it online here, or verify all the test cases at once here.

M++GHGV_fTs*V,]Q1.DEQjCg*5\|smgL\-*L3?d"|X|""   ".[*]1N0Q   Implicit: Q=1st arg, E=2nd arg
Trailing Q inferred
M                                                           Define a function, g(G,H):
++GHG                                                        Return G + H + G
.DEQ                                       Divmod E by Q, yields [E//Q, E%Q]
,]Q1                                           [[Q], 1]
*V                                               Vectorised multiply the two previous results
This yields Q repeated E//Q times, then E%Q
s                                                 Flatten
fT                                                  Filter out falsey values (i.e. trailing 0 if present)
_                                                    Reverse (to put partially filled shelf on top)
V                                                     For N in the above:
]1        
*  N       Repeat the above N times
.[    0Q     Pad the above on the right with 0, to length Q
m                                Map the above, as d, using:
?d"|X|""   "               If d != 0, yield "|X|", else "   "
*L3                           Multiply each char by 3
Yields ['|||','XXX','|||'] or ['   ','   ','   ']
gL\-                              Use g to wrap each element in '-'
s                                 Flatten
g*5\|                                  Use g to add '|||||' to start and end of the above
C                                       Transpose
j                                        Join on newlines, implicit print


# Forth (gforth), 622 bytes (minimized (remove comments, indentation, 1-char word names) to 303 bytes)

My first play with Forth :)

: bar 124 EMIT ;

: delimline ( width -- )
bar
3 * 0 DO 45 EMIT LOOP
bar CR
;

: bookline ( width books -- )
bar
DUP 0 DO bar 88 EMIT bar LOOP
2DUP = IF
DROP DROP
ELSE
- 0 do 3 SPACES LOOP
THEN
bar CR
;

: shelf ( width books -- )
DUP 0 = IF
DROP DROP
ELSE
OVER delimline
3 0 DO OVER OVER bookline LOOP
DROP delimline
THEN
;

: stack ( width books -- )
CR
OVER OVER OVER MOD shelf
OVER /
DUP 0 = IF
DROP DROP
ELSE
0 DO DUP DUP shelf LOOP
THEN
;

6 2 stack
2 6 stack
3 5 stack
4 4 stack


Try it online!

# Output


|------------------|
||X||X|            |
||X||X|            |
||X||X|            |
|------------------|

|------|
||X||X||
||X||X||
||X||X||
|------|
|------|
||X||X||
||X||X||
||X||X||
|------|
|------|
||X||X||
||X||X||
||X||X||
|------|

|---------|
||X||X|   |
||X||X|   |
||X||X|   |
|---------|
|---------|
||X||X||X||
||X||X||X||
||X||X||X||
|---------|

|------------|
||X||X||X||X||
||X||X||X||X||
||X||X||X||X||
|------------|