@@ -32,6 +32,8 @@ type Model struct {
32
32
timer stopwatch.Model
33
33
cfg * config.Config
34
34
fullscreen * FullscreenInfo
35
+ paused bool
36
+ startLine int
35
37
}
36
38
37
39
func InitialModel (level uint , fullscreen * FullscreenInfo ) * Model {
@@ -50,6 +52,7 @@ func InitialModel(level uint, fullscreen *FullscreenInfo) *Model {
50
52
},
51
53
canHold : true ,
52
54
timer : stopwatch .NewWithInterval (time .Millisecond ),
55
+ paused : false ,
53
56
}
54
57
m .bag = tetris .NewBag (len (m .matrix ))
55
58
m .fall = defaultFall (level )
@@ -73,6 +76,8 @@ func InitialModel(level uint, fullscreen *FullscreenInfo) *Model {
73
76
m .styles .ProgramFullscreen .Width (fullscreen .Width ).Height (fullscreen .Height )
74
77
}
75
78
79
+ m .startLine = len (m .matrix )
80
+
76
81
return m
77
82
}
78
83
@@ -81,15 +86,40 @@ func (m Model) Init() tea.Cmd {
81
86
}
82
87
83
88
func (m Model ) Update (msg tea.Msg ) (tea.Model , tea.Cmd ) {
89
+ var cmd tea.Cmd
84
90
var cmds []tea.Cmd
85
91
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
86
99
switch msg := msg .(type ) {
87
100
case tea.KeyMsg :
88
101
switch {
89
102
case key .Matches (msg , m .keys .Quit ):
90
103
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 ())
91
108
case key .Matches (msg , m .keys .Help ):
92
109
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 {
93
123
case key .Matches (msg , m .keys .Left ):
94
124
err := m .currentTet .MoveLeft (& m .matrix )
95
125
if err != nil {
@@ -111,18 +141,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
111
141
panic (fmt .Errorf ("failed to rotate tetrimino counter-clockwise: %w" , err ))
112
142
}
113
143
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 ())
124
146
case key .Matches (msg , m .keys .SoftDrop ):
125
- m .fall . toggleSoftDrop ()
147
+ m .toggleSoftDrop ()
126
148
case key .Matches (msg , m .keys .Hold ):
127
149
err := m .holdTetrimino ()
128
150
if err != nil {
@@ -133,21 +155,24 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
133
155
if m .fall .stopwatch .ID () != msg .ID {
134
156
break
135
157
}
136
- _ , err := m .lowerTetrimino ()
158
+ finished , err := m .lowerTetrimino ()
137
159
if err != nil {
138
160
panic (fmt .Errorf ("failed to lower tetrimino (tick): %w" , err ))
139
161
}
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
+ }
148
169
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
+ }
151
176
152
177
return m , tea .Batch (cmds ... )
153
178
}
@@ -186,6 +211,11 @@ func (m *Model) matrixView() string {
186
211
}
187
212
188
213
func (m * Model ) informationView () string {
214
+ header := ""
215
+ if m .paused {
216
+ header = lipgloss .NewStyle ().Bold (true ).Render ("** PAUSED **" )
217
+ }
218
+
189
219
var output string
190
220
output += fmt .Sprintln ("Score: " , m .scoring .Total ())
191
221
output += fmt .Sprintln ("Level: " , m .scoring .Level ())
@@ -202,7 +232,7 @@ func (m *Model) informationView() string {
202
232
output += fmt .Sprintf ("%06.3f\n " , elapsed )
203
233
}
204
234
205
- return m .styles .Information .Render (output )
235
+ return m .styles .Information .Render (lipgloss . JoinVertical ( lipgloss . Left , header , output ) )
206
236
}
207
237
208
238
func (m * Model ) holdView () string {
@@ -298,14 +328,6 @@ func (m *Model) lowerTetrimino() (bool, error) {
298
328
if ! m .currentTet .CanMoveDown (m .matrix ) {
299
329
action := m .matrix .RemoveCompletedLines (m .currentTet )
300
330
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
309
331
return true , nil
310
332
}
311
333
@@ -316,3 +338,58 @@ func (m *Model) lowerTetrimino() (bool, error) {
316
338
317
339
return false , nil
318
340
}
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