Skip to content

Commit 9919359

Browse files
feat: add pause; add points for soft & hard drops
1 parent c8eeb72 commit 9919359

File tree

4 files changed

+112
-42
lines changed

4 files changed

+112
-42
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ Please feel free to open issues with suggestions, bugs, etc.
2626
- Game over conditions
2727
- Game over screen
2828
- ~~Drop one row immediately if nothing is blocking~~
29-
- Pause ('P' key?)
29+
- ~~Pause ('P' key?)~~
3030
- ~~Fix Tetrimino rotation axis~~
3131
- Implement SRS (Super Rotation System)
32-
- Score points from soft & hard drops
32+
- ~~Score points from soft & hard drops~~
3333
- T-Spins

internal/marathon/fall.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,6 @@ func (f *fall) calculateFallSpeeds(level uint) {
2121
f.softDropTime = time.Microsecond * time.Duration(speed/10)
2222
}
2323

24-
func (f *fall) toggleSoftDrop() {
25-
f.isSoftDrop = !f.isSoftDrop
26-
if f.isSoftDrop {
27-
f.stopwatch.Interval = f.softDropTime
28-
return
29-
}
30-
f.stopwatch.Interval = f.defaultTime
31-
}
32-
3324
func defaultFall(level uint) *fall {
3425
f := fall{}
3526
f.calculateFallSpeeds(level)

internal/marathon/keys.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type keyMap struct {
1212
SoftDrop key.Binding
1313
HardDrop key.Binding
1414
Hold key.Binding
15+
Pause key.Binding
1516
}
1617

1718
func defaultKeyMap() *keyMap {
@@ -25,6 +26,7 @@ func defaultKeyMap() *keyMap {
2526
SoftDrop: key.NewBinding(key.WithKeys("s", "k"), key.WithHelp("s, k", "toggle soft drop")),
2627
HardDrop: key.NewBinding(key.WithKeys("w", "i"), key.WithHelp("w, i", "hard drop")),
2728
Hold: key.NewBinding(key.WithKeys(" "), key.WithHelp("space", "hold")),
29+
Pause: key.NewBinding(key.WithKeys("p"), key.WithHelp("p", "pause")),
2830
}
2931
}
3032

internal/marathon/model.go

Lines changed: 108 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ type Model struct {
3232
timer stopwatch.Model
3333
cfg *config.Config
3434
fullscreen *FullscreenInfo
35+
paused bool
36+
startLine int
3537
}
3638

3739
func InitialModel(level uint, fullscreen *FullscreenInfo) *Model {
@@ -50,6 +52,7 @@ func InitialModel(level uint, fullscreen *FullscreenInfo) *Model {
5052
},
5153
canHold: true,
5254
timer: stopwatch.NewWithInterval(time.Millisecond),
55+
paused: false,
5356
}
5457
m.bag = tetris.NewBag(len(m.matrix))
5558
m.fall = defaultFall(level)
@@ -73,6 +76,8 @@ func InitialModel(level uint, fullscreen *FullscreenInfo) *Model {
7376
m.styles.ProgramFullscreen.Width(fullscreen.Width).Height(fullscreen.Height)
7477
}
7578

79+
m.startLine = len(m.matrix)
80+
7681
return m
7782
}
7883

@@ -81,15 +86,40 @@ func (m Model) Init() tea.Cmd {
8186
}
8287

8388
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
89+
var cmd tea.Cmd
8490
var cmds []tea.Cmd
8591

92+
m.timer, cmd = m.timer.Update(msg)
93+
cmds = append(cmds, cmd)
94+
95+
m.fall.stopwatch, cmd = m.fall.stopwatch.Update(msg)
96+
cmds = append(cmds, cmd)
97+
98+
// Operations that can be performed all the time
8699
switch msg := msg.(type) {
87100
case tea.KeyMsg:
88101
switch {
89102
case key.Matches(msg, m.keys.Quit):
90103
return m, tea.Quit
104+
case key.Matches(msg, m.keys.Pause):
105+
m.paused = !m.paused
106+
cmds = append(cmds, m.timer.Toggle())
107+
cmds = append(cmds, m.fall.stopwatch.Toggle())
91108
case key.Matches(msg, m.keys.Help):
92109
m.help.ShowAll = !m.help.ShowAll
110+
}
111+
case tea.WindowSizeMsg:
112+
m.styles.ProgramFullscreen.Width(msg.Width).Height(msg.Height)
113+
}
114+
115+
if m.paused {
116+
return m, tea.Batch(cmds...)
117+
}
118+
119+
// Operations that can be performed when the game is not paused
120+
switch msg := msg.(type) {
121+
case tea.KeyMsg:
122+
switch {
93123
case key.Matches(msg, m.keys.Left):
94124
err := m.currentTet.MoveLeft(&m.matrix)
95125
if err != nil {
@@ -111,18 +141,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
111141
panic(fmt.Errorf("failed to rotate tetrimino counter-clockwise: %w", err))
112142
}
113143
case key.Matches(msg, m.keys.HardDrop):
114-
for {
115-
finished, err := m.lowerTetrimino()
116-
if err != nil {
117-
panic(fmt.Errorf("failed to lower tetrimino (hard drop): %w", err))
118-
}
119-
if finished {
120-
cmds = append(cmds, m.fall.stopwatch.Reset())
121-
break
122-
}
123-
}
144+
m.hardDrop()
145+
cmds = append(cmds, m.fall.stopwatch.Reset())
124146
case key.Matches(msg, m.keys.SoftDrop):
125-
m.fall.toggleSoftDrop()
147+
m.toggleSoftDrop()
126148
case key.Matches(msg, m.keys.Hold):
127149
err := m.holdTetrimino()
128150
if err != nil {
@@ -133,21 +155,24 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
133155
if m.fall.stopwatch.ID() != msg.ID {
134156
break
135157
}
136-
_, err := m.lowerTetrimino()
158+
finished, err := m.lowerTetrimino()
137159
if err != nil {
138160
panic(fmt.Errorf("failed to lower tetrimino (tick): %w", err))
139161
}
140-
case tea.WindowSizeMsg:
141-
m.styles.ProgramFullscreen.Width(msg.Width).Height(msg.Height)
142-
}
143-
144-
var cmd tea.Cmd
145-
146-
m.timer, cmd = m.timer.Update(msg)
147-
cmds = append(cmds, cmd)
162+
if finished {
163+
if m.fall.isSoftDrop {
164+
linesCleared := m.currentTet.Pos.Y - m.startLine
165+
if linesCleared > 0 {
166+
m.scoring.AddSoftDrop(uint(linesCleared))
167+
}
168+
}
148169

149-
m.fall.stopwatch, cmd = m.fall.stopwatch.Update(msg)
150-
cmds = append(cmds, cmd)
170+
err := m.nextTetrimino()
171+
if err != nil {
172+
panic(fmt.Errorf("failed to get next tetrimino (tick): %w", err))
173+
}
174+
}
175+
}
151176

152177
return m, tea.Batch(cmds...)
153178
}
@@ -186,6 +211,11 @@ func (m *Model) matrixView() string {
186211
}
187212

188213
func (m *Model) informationView() string {
214+
header := ""
215+
if m.paused {
216+
header = lipgloss.NewStyle().Bold(true).Render("** PAUSED **")
217+
}
218+
189219
var output string
190220
output += fmt.Sprintln("Score: ", m.scoring.Total())
191221
output += fmt.Sprintln("Level: ", m.scoring.Level())
@@ -202,7 +232,7 @@ func (m *Model) informationView() string {
202232
output += fmt.Sprintf("%06.3f\n", elapsed)
203233
}
204234

205-
return m.styles.Information.Render(output)
235+
return m.styles.Information.Render(lipgloss.JoinVertical(lipgloss.Left, header, output))
206236
}
207237

208238
func (m *Model) holdView() string {
@@ -298,14 +328,6 @@ func (m *Model) lowerTetrimino() (bool, error) {
298328
if !m.currentTet.CanMoveDown(m.matrix) {
299329
action := m.matrix.RemoveCompletedLines(m.currentTet)
300330
m.scoring.ProcessAction(action, m.cfg.MaxLevel)
301-
m.currentTet = m.bag.Next()
302-
// TODO: Check if the game is over at the starting position
303-
m.currentTet.Pos.Y++
304-
err := m.matrix.AddTetrimino(m.currentTet)
305-
if err != nil {
306-
return false, fmt.Errorf("failed to add tetrimino to matrix: %w", err)
307-
}
308-
m.canHold = true
309331
return true, nil
310332
}
311333

@@ -316,3 +338,58 @@ func (m *Model) lowerTetrimino() (bool, error) {
316338

317339
return false, nil
318340
}
341+
342+
func (m *Model) nextTetrimino() error {
343+
m.currentTet = m.bag.Next()
344+
// TODO: Check if the game is over at the starting position
345+
m.currentTet.Pos.Y++
346+
err := m.matrix.AddTetrimino(m.currentTet)
347+
if err != nil {
348+
return fmt.Errorf("failed to add tetrimino to matrix: %w", err)
349+
}
350+
m.canHold = true
351+
352+
if m.fall.isSoftDrop {
353+
m.startLine = m.currentTet.Pos.Y
354+
}
355+
356+
return nil
357+
}
358+
359+
func (m *Model) hardDrop() {
360+
m.startLine = m.currentTet.Pos.Y
361+
for {
362+
finished, err := m.lowerTetrimino()
363+
if err != nil {
364+
panic(fmt.Errorf("failed to lower tetrimino (hard drop): %w", err))
365+
}
366+
if finished {
367+
break
368+
}
369+
}
370+
linesCleared := m.currentTet.Pos.Y - m.startLine
371+
if linesCleared > 0 {
372+
m.scoring.AddHardDrop(uint(m.currentTet.Pos.Y - m.startLine))
373+
}
374+
m.startLine = len(m.matrix)
375+
376+
err := m.nextTetrimino()
377+
if err != nil {
378+
panic(fmt.Errorf("failed to get next tetrimino (hard drop): %w", err))
379+
}
380+
}
381+
382+
func (m *Model) toggleSoftDrop() {
383+
m.fall.isSoftDrop = !m.fall.isSoftDrop
384+
if m.fall.isSoftDrop {
385+
m.fall.stopwatch.Interval = m.fall.softDropTime
386+
m.startLine = m.currentTet.Pos.Y
387+
return
388+
}
389+
m.fall.stopwatch.Interval = m.fall.defaultTime
390+
linesCleared := m.currentTet.Pos.Y - m.startLine
391+
if linesCleared > 0 {
392+
m.scoring.AddSoftDrop(uint(linesCleared))
393+
}
394+
m.startLine = len(m.matrix)
395+
}

0 commit comments

Comments
 (0)