text:
Watch the bookie win
When you roll a die, you have a 6:1 chance of any given option.
There's a bookmaker taking bets on die rolls. He owns £20 at the start of the game.
The bookmaker offers odds of 4:1, but gamblers take it, because there's money on offer.
There are 10 gamblers. A, B, C, D, E, F, G, H, I, and J. Each gambler owns 10 pounds sterling.
Each round, the 10 gamblers each bet £1 on a die roll, if they have any money left.

If they win their bet, £4 is transferred from the bookmaker to the gambler.
If they lose, £1 is transferred from the gambler to the bookmaker.

The game ends in either of these conditions:

The bookmaker runs out of money, or is in debt.
All of the gamblers have run out of money.

We should see the result presented as a series of tables, like so:
Turn 1
Roll: 2

|                | Bookie | A  | B  | C  | D  | E  | F  | G  | H  | I  | J  |
| Start money    | 20     | 10 | 10 | 10 | 10 | 10 | 10 | 10 | 10 | 10 | 10 |
| Bet            | n/a    | 1  | 3  | 2  | 1  | 2  | 4  | 6  | 5  | 2  | 4  |
| Balance change | -2     | -1 | -1 | +3 | -1 | +3 | -1 | -1 | -1 | +3 | -1 |
| End money      | 18     | 9  | 9  | 13 | 9  | 13 | 9  | 9  | 9  | 13 | 9  |

Turn 2
Roll: 4

|                | Bookie | A  | B  | C  | D  | E  | F  | G  | H  | I  | J  |
| Start money    | 18     | 9  | 9  | 13 | 9  | 13 | 9  | 9  | 9  | 13 | 3  |
| Bet            | n/a    | 1  | 2  | 3  | 4  | 5  | 6  | 1  | 2  | 3  | 4  |
| Balance change | +2     | -1 | -1 | -1 | +3 | -1 | -1 | -1 | -1 | -1 | +3 |
| End money      | 20     | 8  | 8  | 12 | 8  | 12 | 8  | 8  | 8  | 12 | 12 |

When a gambler runs out of money, they can't bet any more. Their bet is 0.
turn 20
roll: 5
|                | Bookie | A  | B  | C  | D  | E  | F  | G  | H  | I  | J  |
| Start money    | 180    | 3  | 1  | 12 | 4  | 0  | 1  | 3  | 6  | 23 | 1  |
| Bet            | n/a    | 1  | 2  | 3  | 4  | 0  | 6  | 1  | 2  | 3  | 4  |
| Balance change | +2     | -1 | -1 | -1 | +3 | 0  | -1 | -1 | -1 | -1 | +3 |
| End money      | 20     | 8  | 8  | 12 | 8  | 0  | 8  | 8  | 8  | 12 | 12 |

Write some code to model this game, and display the results in a table:

Use any language
The gamblers can change their bet each turn, always bet on the same number, all bet on the same number. As long as each turn they bet on one number from 1 to 6.
The die roll must be a random integer from 1 to 6. Use a typical, vernacular definition of random.
The end money from one turn is the start money of the next turn.
The balance change for the bookie is the sum of the other balance changes.
Each turn your program should output:

Which turn we are on
The resulting die roll
The starting balance, die roll, balance change, and ending balance for the bookie and all ten gamblers, as an ascii table.

Attempt to do so in the fewest bytes.

I'm assuming we take no input, and I'm assuming we have to use random for the dice-rolls and bets each round? Also, how does a round look when some of the gamblers have run out of money?

@KevinCruijssen useful questions, as ever :) I said it’s fine for the gamblers to always bet the same way, the die roll has to be random™. I’ll add a broke gambler table.

Does the table need to be formatted exactly as shown in the post, or does it just need to be a legible table of results?

Not to be a pain, but 3:1 odds would mean either £3 bookie->punter or £1 punter->bookie. As written, when I win I bet £1 but don't pay, then get £4 back...

@Mayube It just needs to be recognised as a table, really.

@PhilH Good point, but it puts me in a quandry, I don't know if anyone's part-way through a solution at this moment in time. I concede that you're completely correct, but I'm afraid I'll have to leave the rules as they are, wrong.

@AJFaraday the only issue is it says 3:1 but the rules actually are 4:1, just change the post to say 4:1

hang on. for the die, it's a 5:1 chance

"If they win their bet, £4 is transferred from the bookmaker to the gambler." This is not consistent with the example.

JavaScript (Node.js), 548 516 511 485 453 397 396 392 372 325 323 260 bytes
f=(i=!(b=20,p=10))=>b*p>0?`T:${++i} R:${(r=0|Math.random()*6)+1}
|   |Book|A |B |C |D |E |F |G |H |I |J |
|Stt${(d=(n,s)=>`|${n}`.padEnd(5)+`|${s}`.padEnd(3).repeat(10)+`|
`)(b,p)}|Bet${d('n/a',1)}|Chg${d(u=r?'+10':-40,v=r?-1:'+4')}|End`+d(b+=+u,p+=+v)+f(i):''

Try it online!
-32 something bytes by shortening row labels. Also fixed the dice roll being 0-5, not 1-6
-5 bytes by compacting output format
-26 bytes because padding
-32 bytes because whitespace is the devil
-56 bytes thanks to ASCII-only and Luis Felipe in chat
-1 byte because return x doesn't need to be on a new line
-4 bytes thanks to some small refactors from ASCII-only
-20 bytes thanks to more collaboration in chat and some changes by Kevin Cruijssen
-47 bytes because y was the devil and ASCII-only performed an exorcism
-2 bytes because ASCII-only is a golfing machine
-63 bytes thanks to the work of several people in the comments
The golf I took was calculated, but man am I bad at golf.
Almost looks like python rather than JS because where possible I used newlines in place of semicolons for readability.
All gamblers always bet on 1, so we only need to store 1 gambler balance (p), the bookie's balance is b.
Explanation time
I need to rewrite the explanation to match the 260 byte version, but check the revision history for the lengthy explanation of the 323 byte version.

Hmmm, I might have made a model where the house always loses...

@AJFaraday it does seem like the bookie loses most of the time. Longest game I've seen was a 25 round game where the bookie won though, so that's nice

also @AJFaraday let me know if at any point my format compression becomes too much and the table isn't table-y enough

That's fine with me. It's recognisably a table

Admittedly the format will break if the players hit triple digit balance, or the bookie hits 5 digit balance, but both are impossible due to the nature of the game

389 bytes. Moved i++ and p+=..., and changed the while to a for.

You can do i=x='' and remove i=0 to save 1 byte. Note that hardcoding the header as |   |Book|A |B |C |D |E |F |G |H |I |J | would save 6 more bytes.

285 bytes by making it recursive and applying a few optimizations. (I'd recommend to review it to make sure that I didn't break anything.)

279 bytes

265?

260

Charcoal, 248 bytes
≔²⁰θ≔ＥχχηＷ∧›θ⁰⌈η«Turn: ＩＬ⊞Ｏυω⸿Roll: ≔⊕‽⁶ζＩζ”{⧴&ＤCＺJＷ›B↨⊘ＫBgU→≦”⪫…αχ |  ﹪ |⸿| Start  |%7d |θ⪫Ｅη◧Ｉκ³ |≔Ｅη∧κ⊕‽⁶ε”{➙∧➙⌈℅⎇/s?↨Vp2S>”⪫ε |  ≔⁻ＬΦεκ×⁴ＬΦε⁼κζδ |⸿| Change |◧⁺…+›δ⁰δ⁷ | ⪫Ｅε⎇⁼κζ+3⎇κ-1⁺ ⁰ | ≦⁺δθ﹪ |⸿| End    |%7d |θＵＭη⁻κ∧§ελ∨¬⁼§ελζ±³⪫Ｅη◧Ｉκ³ |¦ |⸿⸿

Try it online! Link is to verbose version of code. Explanation:
≔²⁰θ

The bookie starts with £20.
≔Ｅχχη

Create an array of 10 £10s for the gamblers.
Ｗ∧›θ⁰⌈η«

Repeat the rest of the program while the bookie and at least one gambler have any money left.
Turn: ＩＬ⊞Ｏυω

Print the current turn by pushing a value to the predefined empty list and taking the resulting length.
⸿Roll: ≔⊕‽⁶ζＩζ

Generate and print the next roll.
”{⧴&ＤCＺJＷ›B↨⊘ＫBgU→≦”⪫…αχ

Print the header row.
 |  ﹪ |⸿| Start  |%7d |θ⪫Ｅη◧Ｉκ³ |

Print the starting balances.
≔Ｅη∧κ⊕‽⁶ε

Generate the 10 bets (or 0 if the gambler has no money left).
”{➙∧➙⌈℅⎇/s?↨Vp2S>”⪫ε |

Print the bets.
≔⁻ＬΦεκ×⁴ＬΦε⁼κζδ

Calculate the bookie's change in balance.
 |⸿| Change |◧⁺…+›δ⁰δ⁷ | ⪫Ｅε⎇⁼κζ+3⎇κ-1⁺ ⁰ |

Print the bookie's and gamblers' changes in balance.
≦⁺δθ﹪ |⸿| End    |%7d |θ

Update and print the bookie's balance.
ＵＭη⁻κ∧§ελ∨¬⁼§ελζ±³⪫Ｅη◧Ｉκ³ |¦ |⸿⸿

Update and print the gamblers' balances.

Kotlin, 468 432 bytes
I'm definitely going to try this another way as I'd like it to be around 300. However, it works. Player 1 wins by magically always picking the winning number.
-36 bytes for various golfs I missed last night including @Mayube's fixed bet on 1,
{var o=""
val n=Random()
val m=Array(11){10}
m[0]=20
var t=0
val f={d:Int->o+="%3d|".format(d)}
while(m[0]>0){val b=Array(11){1}
b[1]=n.nextInt(6)+1
val c=Array(11){when{it<1||m[it]<1->0
b[1]==b[it]->3
else->-1}}
o+="\nT:${++t} R:${b[1]}\n|   |Bok|"
('A'..'J').map{o+=" $it |"}
o+="\n|Stt|"
m.map{f(it)}
o+="\n|Bet|n/a|"
(1..10).map{f(b[it])
c[0]-=c[it]}
o+="\n|Chg|"
(0..10).map{f(c[it])
m[it]+=c[it]}
o+="\n|End|"
m.map{f(it)}}
o}

Try it online!

R 417 bytes
g=function(){s=sample
d=10
m=t(matrix(c(20,rep(d,d),NA,s(6,d,T)),11,4,di=list(c("Book",LETTERS[1:d]),c("Stt","Bet","Chg","End"))))
i=1
u=which
while(m[1,1]!=0){
if(m[1,1]==120)break
w=s(6,1)
l=m[1,]!=0
o=m[2,]==0
m[3,u(o&l)]=3
m[3,u(!o&l)]=-1
m[3,u(m[1,]==0)]=0
m[3,1]=-sum(m[3,2:11],na.rm=T)
m[4,]=colSums(m[c(1,3),])
print(kable(m,ca=paste0("T:",i," R:",w),format="pandoc"))
m[1,]=m[4,]
m[2,]=c(NA,s(6,d,T))
i=i+1}}

Explanation:
I was going to post a while ago, but my first attempt was at about 600, after some effort, I whittled it down to 500. After more research, I slowly got down to this result. I'm open to advice regarding better methods.
NOTE: R has a package known as knitr which utilizes the kable function to create tables with various formats (Latex, HTML, pandoc, etc). Each player makes a random guess every turn. Most games end between 26 and 200 or so turns, with the bookie winning every game. Also, variable name choice has little reasoning, I just tried to avoid using builtins as variable names.
g=function(){s=sample          # Abbreviate sample function
d=10                           # Store 10
m=t(matrix(c(20,rep(d,d),NA,s(6,d,T)),11,4,di=list(c("Book",LETTERS[1:d]),c("Stt","Bet","Chg","End"))))
                               # Creates a 11x4 matrix, populates it, names it's columns and rows, and transposes it.
i=1                            # Turn Counter
u=which                        # Abbreviate the which function that tests values to be equivalent to a chosen values
while(m[1,1]!=0){              # While loop with condition on the bookie not having 0 pounds
if(m[1,1]==120)break           # Loop stops if bookie reaches 120 pounds, total
w=s(6,1)                       # Bookie Dice Roll/Winning Die Value
l=m[1,]!=0                     # Check if total pounds of each player equals 0
o=m[2,]==w                     # Check if players choices matches the winning roll
m[3,u(o&l)]=3                  # If guess is correct, and current wages is not 0, store 3
m[3,u(!o&l)]=-1                # If guess is incorrect, and current wages is not 0, store -1
m[3,u(m[1,]==0)]=0             # If current wages is 0, store 0
m[3,1]=-sum(m[3,2:11],na.rm=T) # Add up players totals to get the change to the bookies total
m[4,]=colSums(m[c(1,3),])      # Add the old total to the change
print(kable(m,ca=paste0("T:",i," R:",w),format="pandoc"))   #Print the table with title
m[1,]=m[4,]                    # Store last row in first row
m[2,]=c(NA,s(6,d,T))           # Store new guesses in second row
i=i+1}}                        # Add to the turn counter

C (gcc), 407 406 397 bytes
Each of the gamblers chooses a random number and then always bets on that value.
Thanks to ceilingcat for the suggestions.
#define l(a,b)for(i=11,a;i--;b)
#define p printf
#define q p("%d"s,a[i])
#define z(a)p("\n| "#a s)
#define s"\t| "
a[11],b[11];r,t,i,j,k;f(){l(0,b[i]=i?rand()%6:0)a[i]=10*-~!i;for(t=0;++t;){l(p("\n\nTurn %d\tRoll: %d\n|"s,t,r=rand()%6),p(i?"%c"s:"Book"s,75-i));l(z(Start),q);l(z(Bet),p(i?"%d"s:"-"s,1+b[i]));l(k=!z(Chg),p("%+d"s,i?j:k))k-=j=i?4-5*b[i]!=r:0,*a-=j,a[i]+=j;l(z(End),a[i]||(t=-1))q;}}

Try it online!

Suggest ~9*~!i instead of 10*-~!i and t=a[i]?t:-1 instead of a[i]||(t=-1)


text:
A function to take three integers and return a list of integers and alphabet-letters
Challenge
We take three positive integers a, b, and c as input. Using these integers, first create a sequence in the range [0, c] (inclusive on both ends), in steps of b. For example, for a=4, b=2, c=100, the sequence would be [0,2,4,...,96,98,100].
For every number in this sequence which is divisible by a, replace it with the next letter in the lowercase alphabet, starting with the letter 'a' and  wrapping back around to 'a' after you reach 'z'.
Example:
Input: a=4, b=2, c=100
Output: a2b6c10d14e18f22g26h30i34j38k42l46m50n54o58p62q66r70s74t78u82v86w90x94y98z
Challenge rules:

You can assume that a, b, and c are positive integers only, where b ≤ a ≤ c.
You can assume a is a multiple of b.
You can assume c is divisible by b.
The preferred output is a single concatenated string as above, but a list/array is acceptable as well.

Test cases:
Input: a=4, b=2, c=100
Output:
a2b6c10d14e18f22g26h30i34j38k42l46m50n54o58p62q66r70s74t78u82v86w90x94y98z

Input: a=9, b=3, c=174
Output:
a36b1215c2124d3033e3942f4851g5760h6669i7578j8487k9396l102105m111114n120123o129132p138141q147150r156159s165168t174

Input: a=10, b=2, c=50
Output:
a2468b12141618c22242628d32343638e42444648f

Input: a=25, b=1, c=25
Output:
a123456789101112131415161718192021222324b

Input: a=6, b=6, c=48
Output:
abcdefghi

Input: a=6, b=3, c=48
Output: a3b9c15d21e27f33g39h45i

Input: a=2, b=1, c=100
Output: a1b3c5d7e9f11g13h15i17j19k21l23m25n27o29p31q33r35s37t39u41v43w45x47y49z51a53b55c57d59e61f63g65h67i69j71k73l75m77n79o81p83q85r87s89t91u93v95w97x99y

I'd really like to see an answer in PHP, but this challenge is open to any language. This is codegolf, so the answer should be as short as possible. Standard rules apply for functions/programs and default loopholes are forbidden.

Also consider using The Sandbox to get advice and feedback on questions before posting it to main.

Hi welcome to PPCG! Although I like the challenge itself, the description is lacking a lot of things. As mentioned by @JoKing A primary winning tag is mandatory, where [codegolf] is the most common one I would recommend. Also mentioned by JoKing, making it language specific is not recommended. It's best to open it to all languages instead. As for the challenge itself, please specify a bit more and add a few more test cases. Based on the example I can see the range is [0,c], but this should be clear without looking at the example.

I think all you need to do is change the title to something more descriptive and this is good to go

Is it necessary to output a string where all the numbers/letters are concatenated or can we also return a list?

I prefer concatenation of numbers and letters but a list is also ok

I took the liberty of fixing your challenge so it can be re-opened. Next time please use the Sandbox as advised to perfect a challenge before posting it in the main. Please take a look at what I've edited so you know this for future challenges. If anything is incorrect or I misinterpret something, feel free to edit it again.

Wow thank you @KevinCruijssen , this is really it. Thank you very much and next time I will use the sandbox as advised

@labmann You're welcome. Now all we have to do is wait for re-open votes (you've already got 3 and need 5), and then the answer (hopefully including a PHP one) will come in. PS: You're lucky I had the time to edit your challenge. ;) And that it's a good challenge to begin with, otherwise I wouldn't have bothered. Enjoy your stay, and I indeed hope to see you use the Sandbox to perfect challenges before posting them here next time.

It seems none of your test-cases wrap around from 'z' to 'a'. Could you please include one that does?

PHP, 67 bytes
First of all, thanks for posting such a cool challenge! I really enjoyed solving it :) Now, here's my 67 byte solution:
<?for(;$i<=($p=$argv)[3];$i+=$p[2])echo$i%$p[1]?$i:chr($l++%26+97);

To run it:
php -n <filename> {a} {b} {c}

Or Try it online!

Here's the same solution ungolfed and with explanation comments:
$a      = $argv[1];
$b      = $argv[2];
$c      = $argv[3];
$letter = 'a';
for ($i = 0; $i <= $c; $i += $b) {
    if ($i % $a) { // If $i is divisible by $a, the modulo (%) operator will return a remainder of 0, which PHP sees as a falsey value.
        echo $i;
    } else {
        $letter++;
        $letter %= 26; // Wrap $letter around to `a` after it gets to `z`.
        echo chr($letter + 97); // Use PHP's chr function to get the character. 97 is the index of `a`. http://php.net/manual/en/function.chr.php
    }
}

I did a 60 byte solution, but it doesn't wrap around :(
<?for($l=a;$i<=($p=$argv)[3];$i+=$p[2])echo$i%$p[1]?$i:$l++;

Try it online!

Japt, 15 bytes
;0ôWV £CgX/U ªX

Test it online!
Explanation
;0ôWV £CgX/U ªX    Implicit: U, V, W = inputs
;                  Reset C to the lowercase alphabet (among other variable resets).
 0ôWV              Create the inclusive range [0...W], using a step of V.
      £            Map each item X by this function:
       CgX/U         Get the character at index (X / U) in the lowercase alphabet. If X is
                     not divisible by U, this is a non-integer and the result is undefined.
             ªX      In this case (literally, if falsy), replace it with X.
                   Implicit: output result of last expression

14 bytes taking input in the order c,a,b.

Clojure, 84 79 77  bytes
#(for[i(range 0(inc %3)%2)](if(=(mod i %1)0)(char(+(mod(/ i %1)26)(int \a)))i))

Givin' Lisp a little love
Try it online!
(-7 bytes thanks to @NikoNyrh!)

Welcome to PPCG! Do you know what needs to be added to call the function and print the result on Try it online?

@Laikoni I added a "Try it online" to my post. I also corrected an error in my code, so thanks for reminding me!

Good job :) you can save 3 bytes if you make the outer function via #(...) and use fn to express the inner one. While you are at it you can replace map with for construct and save further 4 bytes.

@NikoNyrh good observation!

And %1 can be replaced by % ;)

R, 65 63 bytes
function(a,b,c,z=seq(0,c,b)){z[x]=rep(letters,sum(x<-!z%%a));z}

Try it online!
2 bytes saved thanks to JayCe!
Returns a list of strings, as R will coerce numbers to strings. To print, simply replace the trailing z with cat(z,sep="").

It looks like the l= is not required. TIO

@JayCe ah of course. I put it there to prevent it from having to generate too many values, but it is un-golfy to do that!

05AB1E, 17 15 bytes
/Ý¹*εD³ÖiA¾è¼}?

-2 bytes thanks to @MagicOctopusUrn.
Takes the input in the order bca.
Try it online or verify all test cases.
Explanation:
/               # Divide the second (implicit) input `c` by the first (implicit) input `b`
                #  i.e. 2 and 100 → 50
 Ý              # Take the range [0, `divided_c`]
                #  i.e. 50 → [0,1,2,...,48,49,50]
  ¹*            # Multiply it with the first input `b`
                #  [0,1,2,...,48,49,50] → [0,2,4,...,96,98,100]
    εD          # For-each in this list:
      ³Öi       #  If the current number is divisible by the third input `a`:
                #   i.e. 8 and `a=4` → 1 (truthy)
                #   i.e. 10 and `a=4` → 0 (falsey)
         A      #   Push the lowercase alphabet
          ¾     #   Push the counter_variable (which starts at 0 by default)
           è    #   Index the alphabet with this counter_variable (wraps around by default)
         ¼      #   Increase the counter_variable by 1
            }   #  Close the if
             ?  #  Output without new-line
                #  (If the if was not entered, the input is implicitly output as else-case)

This seems to be working, but what language is this please. And can I break it into a php function and how?

The language is 05AB1E. A link to the github and is in the title of my post. And I'm afraid this language is completely different than PHP.. 05AB1E is designed for codegolfing, whereas PHP is a hypertext pre-processor for web development. I would advice to edit the challenge description and make it available for all languages, but state you prefer a PHP answer. Once it's re-opened I'm sure someone who is skilled with PHP is able to make a code-golf answer for it. If you need any help editing/fixing your challenge let me know.

/Ý¹*vyD³ÖiA¾è¼}? is 16, just duplicates the y instead of using ë, which ? will ignore if it pushes an alphabet char. Actually, /Ý¹*εD³ÖiA¾è¼}? works for 15 even, because we are over-writing the output array then you can tie the current winner >:).

@MagicOctopusUrn Thanks! :) Forgot about the implicit ë.

JavaScript (Node.js), 62 bytes
(a,b,c)=>(g=k=>k>c?'':(k%a?k:Buffer([97+x++%26]))+g(k+b))(x=0)

Try it online!

Java 10, 93 83 bytes
(a,b,c)->{var r="";for(int i=0;i<=c;i+=b)r+=i%a<1?(char)(i/a%26+97):i+"";return r;}

Try it online here. Thanks to Scrooble for golfing 10 bytes.
Ungolfed:
(a, b, c) -> { // lambda taking 3 integer arguments and returning a String
    var r = ""; // we build the result String in steps
    for(int i = 0; i <= c; i+=b) // loop over the range [0,c] in steps of b
        r += i % a < 1 ? (char) (i / a % 26 + 97) : "" + i; // if i is a multiple of a, append the next letter to r as a char, else append i
    return r; // return the result
}

Perl 6,  60  50 bytes
->\a,\b,\c{[~] (0,b...c).map:{$_%a??$_!!('a'..'z')[$++%26]}}

Test it
{$^a;(0,$^b...$^c).map:{$_%$a??$_!!chr 97+$++%26}}

Test it
{  # bare block lambda with parameters $a,$b,$c

  $^a; # declare param $a, but don't use it
       # (so $a can be used in the inner block)

  (
    0, $^b ... $^c  # the base sequence

  ).map:            # for each of those values
  {
    $_ % $a         # is it not divisible by `$a` ( shorter than `%%` )

    ??  $_          # if it isn't divisible just return it

    !!              # otherwise return the following

        chr         # a character from the following number

          97 +      # 'a'.ord +
          $++       # self incrementing value (starts at 0)
          % 26      # wrap around back to 'a'
  }
}

You're losing a byte by using ->\a,\b,\c: use just $^a etc. And chr 97+$++%26 is quite a bit shorter than ("a".."z")[$++%26]. Finally, you can omit the concatenation because the challenge does not require it. That would save 10 bytes in total, leaving 50

CJam, 23 bytes
q~),@/f%{0W):W26%'a+t}%

Try it online!
q~                              a b c
  ),                            a b [0…c]
    @/                          b [[0…a-1] [a…2a-1] ... […c]]
      f%                        [[0 b…a-b] [a a+b…2a-b] ... […c]]

        {0          t}%         Replace each first with:
          W):W26%'a+            ++W % 26 + 'a'

Reading input as b a c and dropping the @ would be 22 bytes (not sure if that's valid).

C (gcc), 74  72 bytes
i,l;f(a,b,c){for(i=l=0;i<=c;i+=b)i%a?printf("%d",i):putchar(l++%26+97);}

Try it online!

Python 2, 80 70 bytes
Returns a list, since the OP said that is acceptable.
lambda a,b,c:map(lambda x:x if x%a else chr(x/a%26+97),range(0,c+1,b))

Very different approach from the other Python 2 answer.
Try it online!

Python 2, 65 63 bytes
Returns a list, since the OP said that is acceptable.
lambda a,b,c:[[chr(n/a%26+97),n][n%a>0]for n in range(0,c+1,b)]

Try it online!

Previous versions used during the creative process (working backwards):
84 bytes
Returns a generator expression.
def f(a,b,c,x=0):
    for n in range(0,c+1,b):yield n%a and n or chr(x%26+97);x+=n%a==0

111 bytes
Uses a generator for the alphabet and returns a list.
def g(x=0):
    while 1:yield chr(x%26+97);x+=1
A=g()
f=lambda a,b,c:[n%a and n or next(A)for n in range(0,c+1,b)]

Ah, right. That wasn't an option when I was using next. Nice.

Haskell, 71 69 bytes
(a#b)c=do n<-[0,b..c];[show n,[['a'..]!!mod(div n a)26]]!!(0^mod n a)

Try it online!

Previous 71 bytes:
(a#b)c=do n<-[0,b..c];last$show n:[[['a'..]!!mod(div n a)26]|mod n a<1]

Try it online!
Explanation:
(a#b)c=                         -- given the inputs a, b and c
  do n<-[0,b..c];               -- take n from the range from 0 to c with increments of b
  last$   :[   |mod n a<1]      -- if n is not divisible by a
       show n                   -- then use n converted to a string
            [   mod(div n a)26] -- otherwise divide n by a
             ['a'..]!!          -- and use the character at this position in the alphabet

Charcoal, 22 bytes
ＮθＮη⭆Φ⊕Ｎ¬﹪ιη⎇﹪ιθι§β÷ιθ

Try it online! Link is to verbose version of code. Explanation:
Ｎθ                      Input `a`
  Ｎη                    Input `b`
       Ｎ                Third input (`c`)
      ⊕                 Increment
     Φ                  Filter over implicit range
           η            `b`
          ι             Current value
         ﹪              Modulo
        ¬               Not (i.e. divisible)
    ⭆                   Map over result and join
              ι         Current value
               θ        `a`
             ﹪          Modulo
            ⎇           Ternary
                ι       Current value
                    ι   Current value
                     θ  `a`
                   ÷    Divide
                  β     Predefined lowercase alphabet
                 §      Cyclically index

PHP, 79 bytes
Note: Not too good at PHP
<?for(@$l=a;$i<=($p=$argv)[3];$i+=$p[2])$o.=$i%$p[1]?$i:substr($l++,-1);echo$o;

Try it online!

PHP, 84 bytes
This is just the same above code just in function format (since you wanted a PHP answer maybe this is better for you)
function f($a,$b,$c,$l=a){for(;$i<=$c;$i+=$b)$o.=$i%$a?$i:substr($l++,-1);return$o;}

Try it online!

Excel VBA, 76 bytes
Takes input, a,b, and c from [A1], [B1] and [C1], respectively then outputs to the VBE immediate window.
For i=0To[C1]Step[B1]:j=i Mod[A1]:?IIf(j,i,Chr(97+k)):k=(k-(j=0))Mod 26:Next

Python 2, 83 bytes
f=lambda a,b,c,s=0,p=0:c>=s and[chr(p%26+97),`s`][s%a>0]+f(a,b,c,s+b,p+(s%a<1))or''

Try it online!

Kotlin, 80 79 bytes
-1 thanks to O.O.Balance!
{a,b,c->var s="";for(x in 0..c step b)s+=if(x%a>0)x else(x/a%26+97).toChar();s}

Try it online!
My first time golfing (or doing anything else) in Kotlin! Probably can be improved.
Very similar to this Java answer, I realized after writing it. Save on the return and lose on the ternary for almost the same score.

It's not much, but -1 byte by omitting the space after the else.

Python3 - 111, 101, 98, 94 bytes
q=iter(map(chr,range(97,123)))
[next(q)if x%a==0else x for x in[y*b for y in range((c+b)//b)]]

Probably not the shortest, but there's room for improvement
Try it online!

You can remove a few spaces next(q)if x%a==0else x for x in[y*b

@mbomb007 Good call, didn't realize you could do that. Thanks!

You may like to view Tips for golfing in Python

CJam, 64 63 bytes (ouch)
97:Y;riri:Bri\/),[{B*}*]{X\_@\%0={Yc\Y):Y'{i={97:Y;}&}{X\}?}fX;

 Try it online!
Explanation
97:Y;                                                                 Set a variable "Y" to the ASCII value of 'a' and pop the variable from the stack
     ri                                                               Read one chunk of the input (Space-delimited)
       ri:B                                                           Set a variable "B" to the next input chunk
           ri\/                                                       Divide the next input chunk by the top value in the stack (B)
               1+                                                     Add one to make the values inclusive
                 ,                                                    Turn it into an array
                   {B*}*                                              Multiply all the array values by B
                  [     ]                                             Capture the results in an array
                         {                                   }fX      Massive for loop
                          X\_@\%0=                                    If X modulo (A value) equals zero
                                  {                   }               If condition true
                                   Yc\                                Push the character with an ASCII value of Y
                                      Y):Y                            Increase the value of Y
                                          '{i=                        If Y's value is that same as that of "{" (the character after z in ASCII)
                                              {97:Y;}                 Set Y's value back to ASCII 'a'
                                                     &                If-only flag
                                                       {  }           If condition false (from X\_@\%0=)
                                                        X\            Push X onto the stack
                                                           ?          If-else flag
                                                               ;      Pop A value from the stack

This could definitely be made better so feel free to join in!

Changes
Helen cut off a byte!
Old: 97:Y;riri:Bri\/1+,[{B*}*]{X\_@\%0={Yc\Y):Y'{i={97:Y;}&}{X\}?}fX;
New: 97:Y;riri:Bri\/),[{B*}*]{X\_@\%0={Yc\Y):Y'{i={97:Y;}&}{X\}?}fX;
By changing 1+ to ) we can cut off a byte.

Jelly, 14 bytes
Żmµ⁵ḍaÄ$aị¥Øao

Try it online!

Python 2 and 3 - 123 128 Bytes
d=-1
lambda a,b,c:[s()if i%a<1else i for i in range(0,c+1,b)]
def s():global d;d=(d+1)%26;return'abcdefghijklmnopqrstuvwxyz'[d]

You'll need to put f= in front of the lambda and then call it with f(a,b,c) but I'm pretty sure that's allowed.

Jelly, 16 bytes
Żmọ⁵œpƊżJṖØaṁƲ$F

A full program taking arguments c b a which prints the result.
Try it online!

C (gcc/clang), 80 68 bytes
f(a,b,c,i){for(i=0;i<=c;i+=b)i%a?printf("%d",i):putchar(i/a%26+97);}

Port of my Java answer. Try it online here.

Suggest printf(L"搥挥"+!(i%a),i%a?i:i/a%26+97) instead of i%a?printf("%d",i):putchar(i/a%26+97)

Clojure, 117 bytes
#(loop[R""A 0 i 0](if(> i %3)R(let[?(=(mod i %)0)](recur(str R(if ?(char(+(mod A 26)97))i))(if ?(inc A)A)(+ i %2)))))

Such imperative. :/

Ruby, 59 58 55 bytes
->a,b,c{[w=?a]+b.step(c,b).map{|x|x%a>0?x:w=w.next[0]}}

Try it online!
Returns a list

source: Stack Exchange

license: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/4.0/

sort: votes


text:
Greet according to the time of day
Challenge
Write a program that greets a user depending on what time of day (GMT) it is.

Output
A string (printed or returned) according to these rules:

If the time is 06:00 to 11:59 the greeting has to be "Good morning"
If the time is 12:00 to 17:59 the greeting has to be "Good afternoon"
If the time is 18:00 to 19:59 the greeting has to be "Good evening"
If the time is 20:00 to 05:59 the greeting has to be "Zzz"

Clarifications

There is no input
The times and greetings must be exactly as shown
The time zone is +0 GMT

Examples
Time: 14:45 -> Good afternoon

Time: 05:59 -> Zzz

Time: 9:23 -> Good morning

Time: 18:00 -> Good evening

Without a testing environment in which one can manipulate the current time, how would you test this? Wouldn't it be better for the code to receive the current time as input instead?

If not taking input should we be using UTC / local timezone according to some OS spec / whatever we want (needs some restriction though as it's always morning somewhere in the world)

I think you forgot about Good night...

@mbomb007 the program is sleeping at night

Well, there goes my Novell login script solution - its %GREETING_TIME variable doesn't have "Zzz", it just switches from "evening" to "morning" at midnight.

I recommend against restricting to a specific time zone, but am in favor of restricting to your own system time instead. Many answers are currently invalid because of it.

JavaScript (ES6), 87 bytes
As noticed by TFeld, my original formula was overcomplicated. We can just do:
_=>['Zzz','Good morning','Good afternoon','Good evening'][new Date().getHours()%20/6|0]

Try it online!
Or test this version that takes the hour as parameter.

JavaScript (ES6), 90 bytes
_=>['Zzz','Good morning','Good afternoon','Good evening'][new Date().getHours()*7%20%7>>1]

Try it online!
Or test this version that takes the hour as parameter.
Formula
Given the current hour \$h\$, we find the appropriate greeting index \$i\$ with:
$$i=\left\lfloor\frac{((7\times h) \bmod 20) \bmod 7}{2}\right\rfloor$$
A good thing about this formula is that it does not require any parenthesis once converted to JS:
h * 7 % 20 % 7 >> 1

Table
 Hour |  *7 | mod 20 | mod 7 | >> 1
------+-----+--------+-------+------
   0  |   0 |    0   |   0   |   0
   1  |   7 |    7   |   0   |   0
   2  |  14 |   14   |   0   |   0
   3  |  21 |    1   |   1   |   0
   4  |  28 |    8   |   1   |   0
   5  |  35 |   15   |   1   |   0
   6  |  42 |    2   |   2   |   1
   7  |  49 |    9   |   2   |   1
   8  |  56 |   16   |   2   |   1
   9  |  63 |    3   |   3   |   1
  10  |  70 |   10   |   3   |   1
  11  |  77 |   17   |   3   |   1
  12  |  84 |    4   |   4   |   2
  13  |  91 |   11   |   4   |   2
  14  |  98 |   18   |   4   |   2
  15  | 105 |    5   |   5   |   2
  16  | 112 |   12   |   5   |   2
  17  | 119 |   19   |   5   |   2
  18  | 126 |    6   |   6   |   3
  19  | 133 |   13   |   6   |   3
  20  | 140 |    0   |   0   |   0
  21  | 147 |    7   |   0   |   0
  22  | 154 |   14   |   0   |   0
  23  | 161 |    1   |   1   |   0

Python 2, 120 106 102 bytes
import time
print['Zzz','Good morning','Good afternoon','Good evening'][int(time.time()/3600%24%20/6)]

Try it online!
Testable here: Try it online!

Similar to Arnauld's answer, but slightly different:
h  %20 /6
---------
0   0   0
1   1   0
2   2   0
3   3   0
4   4   0
5   5   0
6   6   1
7   7   1
8   8   1
9   9   1
10  10  1
11  11  1
12  12  2
13  13  2
14  14  2
15  15  2
16  16  2
17  17  2
18  18  3
19  19  3
20  0   0
21  1   0
22  2   0
23  3   0

Saved:

-3 bytes, thanks to Arnauld
-1 byte, thanks to N. P.

I think you can do /3600%24%20/6.

I think you can save a byte by importing time and using time.time() as well

@JonathanAllan Doesn't work unfortunately. It's still a float

Oh my mistake, you need to index with it :(

R, 97 95 93 Bytes
Using methods found above in R
c("Zzz","Good morning","Good afternoon","Good evening")[as.POSIXlt(Sys.time(),"G")$h%%20/6+1]

Explanation:
c("Zzz","Good morning","Good afternoon","Good evening")      # Creates a vector with the greetings
[                                                            # Open bracket. The number in the bracket will extract the corresponding greeting from the vector below
as.POSIXlt(                                                  # as.POSIXlt converts the object to one of the two classes used to represent date/times
Sys.time(),                                                  # Retrieves the current time on the OS
"G")                                                         # Converts the time to the desired time zone. Does output a warning, but still converts properly to GMT
$h                                                           # Extracts the hour from the object created by as.POSIXlt
%%20/6                                                       # Methodology as used by other golfers
+1]                                                          # Vectors in R start from 1, and not 0 like in other languages, so adding 1 to the value ensures values range from 1 to 4, not 0 to 3

Example
Notice how this line of code, without adding 1, is short 10 elements
c('Zzz','Good morning','Good afternoon','Good evening')[0:23%%20/6]

[1] "Zzz"            "Zzz"            "Zzz"            "Zzz"            "Zzz"            "Zzz"
[7] "Good morning"   "Good morning"   "Good morning"   "Good morning"   "Good morning"   "Good morning"
[13] "Good afternoon" "Good afternoon"

Adding 1 ensures that the result obtained is greater than 0
c('Zzz','Good morning','Good afternoon','Good evening')[as.integer(0:23)%%20/6+1]

[1] "Zzz"            "Zzz"            "Zzz"            "Zzz"            "Zzz"            "Zzz"
[7] "Good morning"   "Good morning"   "Good morning"   "Good morning"   "Good morning"   "Good morning"
[13] "Good afternoon" "Good afternoon" "Good afternoon" "Good afternoon" "Good afternoon" "Good afternoon"
[19] "Good evening"   "Good evening"   "Zzz"            "Zzz"            "Zzz"            "Zzz"

Welcome to PPCG! I think you can just divide by 6 regularly to save 2 bytes. You may need to turn it into a function as well, something like this: Try it online!.

@BLT Thank You. When I tested the code in pieces / was yielding floating point numbers, which I didn't think would work in the brackets, thus I used %/% for floor division. I learned something new today. As for turning it into a function, Sys.time() automatically retrieves the current time on any given computer, itself being the sole input in the equation.

It seems like we can use timezone "G" and it'll autocomplete to "GMT". It gives a warning but outputs GMT on my UK-based Windows 10 machine, though it may be region/platform specific? I don't see anything about it in the docs. Saves 2 bytes if we can use it though.

@CriminallyVulgar That works here on my US-based Windows 10 machine. Thanks!

T-SQL, 153 bytes
SELECT CASE WHEN a>18THEN'Good afternoon'WHEN a>12THEN'Good morning'WHEN a>2THEN'Zzz'ELSE'Good evening'END
FROM(SELECT(DATEPART(hh,GETUTCDATE())+6)%24a)a

Try the SQL Fiddle
Explanation:
SELECT
  CASE WHEN a>18 THEN'Good afternoon' --CASE returns the first value in order whose condition is met
       WHEN a>12 THEN'Good morning'
       WHEN a>2  THEN'Zzz'
       ELSE 'Good evening'
       END
FROM( SELECT (      --Use a subquery to treat the time like a 1x1 table - shorter than declaring a variable
    DATEPART(hh,    --Returns the hour part (hh) of the date
      GETUTCDATE()) --Returns current UTC time (as far as the SQL Server is concerned)
        +6)%24      --Virtual time zone where no period crosses midnight, so everything is ordered
        a)          --Give column a 1-letter alias
        a           --Give subquery an alias so SQL Server doesn't complain

Excel, 97 bytes
=VLOOKUP(Hour(Now()),{0,"Zzz";6,"Good morning";12,"Good afternoon";18,"Good evening";20,"Zzz"},2)

Start with the list of cases

0 <= HOUR < 6 : "Zzz"
  6 <= HOUR < 12 : "Good morning"
  12 <= HOUR < 18 : "Good afternoon"
  18 <= HOUR < 20 : "Good evening"
  20 <= HOUR : "Zzz"

Then just use a Range-based Vlookup (default if you ommit the 4th argument) with an Array of values:

H= 0 | "Zzz"
H= 6 | "Good morning"
H=12 | "Good afternoon"
H=18 | "Good evening"
H=20 | "Zzz"

{0,"Zzz"; 6,"Good morning"; 12,"Good afternoon"; 18, "Good evening"; 20,"Zzz"}

I experimented with using MOD(HOUR(NOW())+18,24 to shift the hour back by 6 and reduce the cases by 1, but that resulted in 99 bytes. :(

Perl 5, 77 bytes
$_=(gmtime)[2]%20;say$_<6?Zzz:"Good ".($_<12?morning:$_<18?afternoon:evening)

Try it online!

05AB1E, 36 bytes
”‚¿”…•´¯âžÖ#1ú«…Zzz¸«ža•1Ý;`{ùí4²•èè

Try it online!

”‚¿”                                 # Push 'Good'
    …•´¯âžÖ                          # Push 'morning afternoon evening'
           #                         # Split on spaces.
            1ú                       # Pad each with 1 space.
              «                      # Concat good onto it: ['Good morning',...,'Good evening']
               …Zzz¸«                # Concat 'Zzz' onto it.
                     ža              # Get current hour.
                       •1Ý;`{ùí4²•   # Push 33333000000111111222.
                                  è  # indexes[current_hour]->i[6:00AM]->0
                                   è # phrases[indexes[current_hour]]->p[i[6AM]]->'Good morning'.

C# (Visual C# Interactive Compiler), 91 bytes
Write("Zzz,Good morning,Good afternoon,Good evening".Split(',')[DateTime.UtcNow.Hour%20/6])

Try it online!
Uses Utc Now to get the UTC time.
If it can be a function instead of a full program:
C# (Visual C# Interactive Compiler), 88 bytes
()=>"Zzz,Good morning,Good afternoon,Good evening".Split(',')[DateTime.UtcNow.Hour%20/6]

Try it online!
Test with all the hours(code stolen shamelessly from Arnauld)

This is pretty good! Although you probably should use DateTime.UtcNow to get the correct timezone.

You could remove the trailing semicolon (;) character though!

The semicolon totally slipped by me. Thanks!

Common Lisp, 103 bytes
(nth(floor(mod(nth-value 2(get-decoded-time))20)6)'("Zzz""Good morning""Good afternoon""Good evening"))

Ungolfed
(nth (floor (mod (nth-value 2 (get-decoded-time))
                 20)
            6)
     '("Zzz" "Good morning" "Good afternoon" "Good evening"))

Common Lisp mostly ignores whitespace as long as it can unambiguously determine where each sub-expression ends, so much of the golfing here is just removing whitespace. Common Lisp also provides the ability for functions to return multiple values, with all but the first discarded if the caller hasn't explicitly requested the 2nd/3rd/etc return values. This allows functions to return auxiliary data, like the floor function performs floor division, but as a secondary return value returns the remainder. This also allows for functions to avoid the overhead of having to package up their return values in a datastructure if the caller is likely to immediately destructure it again. (get-decoded-time) (really itself a shorthand for (decode-universal-time (get-universal-time))) returns the most values of just about any standard function in common lisp... 9, to be exact. The 3rd return value ((nth-value 2 ...) ) is the current hour in 24 hour time. Then it's just computing the proper index into the list of responses and passing that to nth. I have to use floor here as CL would return a proper fraction if I used / for division.
Try it online!

C# (.NET Core), 177 bytes
using System;namespace a{class b{static void Main(string[] a){Console.WriteLine(new String[]{"Zzz","Good morning","Good afternoon","Good evening"}[DateTime.Now.Hour%20/6|0]);}}}

Try it online!

139 bytes

Japt, 52 bytes
`Zzz,Good ¶rÍÁ,Good af’rÍ9,Good evÀxg`q, gKd %20/6|0

Try it online!

Powershell, 82 72 bytes
Port of TFeld's answer
('Zzz','Good morning','Good afternoon','Good evening')[(date).Hour%20/6]

Batch, 178 bytes
@for /f "tokens=3" %%a in ('wmic path Win32_UTCTime') do @set/ag=%%a%%20/6
@for %%a in (Zzz.0 "Good morning.1" "Good afternoon.2" "Good evening.3") do @if %%~xa==.%g% echo %%~na

Uses @TFeld's formula. Locale-dependent version is only 128 bytes:
@set/ag=%time:~,2%%%20/6
@for %%a in (Zzz.0 "Good morning.1" "Good afternoon.2" "Good evening.3") do @if %%~xa==.%g% echo %%~na

C (gcc), 127 bytes
The only really sneaky trick is coercing the hours into unsigned int so that I can force the night values to >14. As the struct tm structure only has integers, I can pretend that it's an array being passed from gmtime_r.
char*s[]={"morning","afternoon","evening"};t,u[9];f(){t=time(0);gmtime_r(&t,u);printf(u[2]-6U>13?"Zzz":"Good %s",s[u[2]/6-1]);}

Try it online!

Suggest (char*[]){"morning","afternoon","evening"} instead of s

05AB1E, 45 42 bytes
ža7*20%7%2÷U”‚¿”Xi'•´ëX<i'¯âë'žÖ]ðýX>i…Zzz

(h*7%20%7)//2 ported from @Arnauld's JavaScript (ES6) answer.
Try it online or verify all hours.

Original 45 bytes answer:
žaDV6÷U”‚¿”Xi'•´ëX<i'¯âë'žÖ]ðýX3%>iY2÷9Êi…Zzz

Try it online or verify all hours.
Explanation:
ža                 # Get the current hours
  DV               # Duplicate it, and store it in variable `Y`
    6÷U            # Integer-divide it by 6, and store it in variable `X`
”‚¿”               # String "Good"
    Xi             # If `X` is 1:
      '•´          #  Use string 'morning'
    ëX<i           # Else-if `X` is 2:
        '¯â        #  Use string 'afternoon'
    ë              # Else:
     'žÖ           #  Use string 'evening'
           ]       # Close all if-elses
            ðý     # Join the two string literals with a space
X3%>i              # If `X` modulo-3 is 0:
     Y2÷9Êi        # And if `Y` integer-divided by 2 is not 9:
           …Zzz    #  Output string "Zzz" instead
                   # (Implicit else:
                   #   output top of stack, which is the "Good ...")

See the explanation here to understand why ”‚¿” is "Good"; '•´ is "morning"; '¯â is "afternoon"; and 'žÖ is "evening".

Noether, 106 bytes
"Good "~g{2D~d5>d12<&}{g"morning"+P}d{11>d18<&}{g"afternoon"+P}d{17>d20<&}{g"evening"+P}d{20>d6<&}{"Zzz"P}

Try it online!
The command 2D returns the hour part of the current the time and the rest is a load of if statements.

Haskell, 174 bytes
import Data.UnixTime
v h|h<10="Zzz"|1>0="Good "++y h
y h|h<16="morning"|h<22="afternoon"|1>0="evening"
main=do;t<-getUnixTime;print$v$mod(div(read.show.utSeconds$t)3600+4)24

J, 65 bytes
Another port of TFeld's answer.
('Zzz','Good '&,&>;:'morning afternoon evening'){~<.6%~20|3{6!:0''

3{6!:0'' gets the hour, which is index 3 of the current time vector, from the builtin 6!:0''
<.6%~20| is floor of ((hour mod 20) divided by 6)
use that as an index {~ into a 4x14 character array containing the greetings.
save a few chars by concatenating (,) the string Good to the words morning, afternoon, and evening which are split on whitespace by the J "words" tokenizer (;:).  It technically is for tokenizing J sentences, but since J has bare words it ends up splitting on whitespace.

all_licenses: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/4.0/