v

Зеркало из https://github.com/vlang/v
Форк
0
/
pong.v 
505 строк · 9.7 Кб
1
// Copyright (c) 2020 Lars Pontoppidan. All rights reserved.
2
// Use of this source code is governed by the MIT license distributed with this software.
3
import term
4
import term.ui
5
import time
6

7
enum Mode {
8
	menu
9
	game
10
}
11

12
const player_one = 1 // Human control this racket
13

14
const player_two = 0 // Take over this AI controller
15

16
const white = ui.Color{255, 255, 255}
17
const orange = ui.Color{255, 140, 0}
18

19
@[heap]
20
struct App {
21
mut:
22
	tui    &ui.Context = unsafe { nil }
23
	mode   Mode        = Mode.menu
24
	width  int
25
	height int
26
	game   &Game = unsafe { nil }
27
	dt     f32
28
	ticks  i64
29
}
30

31
fn (mut a App) init() {
32
	a.game = &Game{
33
		app: a
34
	}
35
	w, h := a.tui.window_width, a.tui.window_height
36
	a.width = w
37
	a.height = h
38
	term.erase_del_clear()
39
	term.set_cursor_position(
40
		x: 0
41
		y: 0
42
	)
43
}
44

45
fn (mut a App) start_game() {
46
	if a.mode != .game {
47
		a.mode = .game
48
	}
49
	a.game.init()
50
}
51

52
fn (mut a App) frame() {
53
	ticks := time.ticks()
54
	a.dt = f32(ticks - a.ticks) / 1000.0
55
	a.width, a.height = a.tui.window_width, a.tui.window_height
56
	if a.mode == .game {
57
		a.game.update()
58
	}
59
	a.tui.clear()
60
	a.render()
61
	a.tui.flush()
62
	a.ticks = ticks
63
}
64

65
fn (mut a App) quit() {
66
	if a.mode != .menu {
67
		a.game.quit()
68
		return
69
	}
70
	term.set_cursor_position(
71
		x: 0
72
		y: 0
73
	)
74
	exit(0)
75
}
76

77
fn (mut a App) event(e &ui.Event) {
78
	match e.typ {
79
		.mouse_move {
80
			if a.mode != .game {
81
				return
82
			}
83
			// TODO: mouse movement for real Pong sharks
84
			// a.game.move_player(player_one, 0, -1)
85
		}
86
		.key_down {
87
			match e.code {
88
				.escape, .q {
89
					a.quit()
90
				}
91
				.w {
92
					if a.mode != .game {
93
						return
94
					}
95
					a.game.move_player(player_one, 0, -1)
96
				}
97
				.a {
98
					if a.mode != .game {
99
						return
100
					}
101
					a.game.move_player(player_one, 0, -1)
102
				}
103
				.s {
104
					if a.mode != .game {
105
						return
106
					}
107
					a.game.move_player(player_one, 0, 1)
108
				}
109
				.d {
110
					if a.mode != .game {
111
						return
112
					}
113
					a.game.move_player(player_one, 0, 1)
114
				}
115
				.left {
116
					if a.mode != .game {
117
						return
118
					}
119
					a.game.move_player(player_two, 0, -1)
120
				}
121
				.right {
122
					if a.mode != .game {
123
						return
124
					}
125
					a.game.move_player(player_two, 0, 1)
126
				}
127
				.up {
128
					if a.mode != .game {
129
						return
130
					}
131
					a.game.move_player(player_two, 0, -1)
132
				}
133
				.down {
134
					if a.mode != .game {
135
						return
136
					}
137
					a.game.move_player(player_two, 0, 1)
138
				}
139
				.enter, .space {
140
					if a.mode == .menu {
141
						a.start_game()
142
					}
143
				}
144
				else {}
145
			}
146
		}
147
		else {}
148
	}
149
}
150

151
fn (mut a App) free() {
152
	unsafe {
153
		a.game.free()
154
		free(a.game)
155
	}
156
}
157

158
fn (mut a App) render() {
159
	match a.mode {
160
		.menu { a.draw_menu() }
161
		else { a.draw_game() }
162
	}
163
}
164

165
fn (mut a App) draw_menu() {
166
	cx := int(f32(a.width) * 0.5)
167
	y025 := int(f32(a.height) * 0.25)
168
	y075 := int(f32(a.height) * 0.75)
169
	cy := int(f32(a.height) * 0.5)
170

171
	a.tui.set_color(white)
172
	a.tui.bold()
173
	a.tui.draw_text(cx - 2, y025, 'VONG')
174
	a.tui.reset()
175
	a.tui.draw_text(cx - 13, y025 + 1, '(A game of Pong written in V)')
176

177
	a.tui.set_color(white)
178
	a.tui.bold()
179
	a.tui.draw_text(cx - 3, cy + 1, 'START')
180
	a.tui.reset()
181

182
	a.tui.draw_text(cx - 9, y075 + 1, 'Press SPACE to start')
183
	a.tui.reset()
184
	a.tui.draw_text(cx - 5, y075 + 3, 'ESC to Quit')
185
	a.tui.reset()
186
}
187

188
fn (mut a App) draw_game() {
189
	a.game.draw()
190
}
191

192
struct Player {
193
mut:
194
	game        &Game = unsafe { nil }
195
	pos         Vec
196
	racket_size int = 4
197
	score       int
198
	ai          bool
199
}
200

201
fn (mut p Player) move(x f32, y f32) {
202
	p.pos.x += x
203
	p.pos.y += y
204
}
205

206
fn (mut p Player) update() {
207
	if !p.ai {
208
		return
209
	}
210
	if isnil(p.game) {
211
		return
212
	}
213
	// dt := p.game.app.dt
214
	ball := unsafe { &p.game.ball }
215
	// Evil AI that eventually will take over the world
216
	p.pos.y = ball.pos.y - int(f32(p.racket_size) * 0.5)
217
}
218

219
struct Vec {
220
mut:
221
	x f32
222
	y f32
223
}
224

225
fn (mut v Vec) set(x f32, y f32) {
226
	v.x = x
227
	v.y = y
228
}
229

230
struct Ball {
231
mut:
232
	pos Vec
233
	vel Vec
234
	acc Vec
235
}
236

237
fn (mut b Ball) update(dt f32) {
238
	b.pos.x += b.vel.x * b.acc.x * dt
239
	b.pos.y += b.vel.y * b.acc.y * dt
240
}
241

242
@[heap]
243
struct Game {
244
mut:
245
	app     &App = unsafe { nil }
246
	players []Player
247
	ball    Ball
248
}
249

250
fn (mut g Game) move_player(id int, x int, y int) {
251
	mut p := unsafe { &g.players[id] }
252
	if p.ai { // disable AI when moved
253
		p.ai = false
254
	}
255
	p.move(x, y)
256
}
257

258
fn (mut g Game) init() {
259
	if g.players.len == 0 {
260
		g.players = []Player{len: 2, init: Player{ // <- BUG omitting the init will result in smaller racket sizes???
261
			game: g
262
		}}
263
	}
264
	g.reset()
265
}
266

267
fn (mut g Game) reset() {
268
	mut i := 0
269
	for mut p in g.players {
270
		p.score = 0
271
		if i != player_one {
272
			p.ai = true
273
		}
274
		i++
275
	}
276
	g.new_round()
277
}
278

279
fn (mut g Game) new_round() {
280
	mut i := 0
281
	for mut p in g.players {
282
		p.pos.x = if i == 0 { 3 } else { g.app.width - 2 }
283
		p.pos.y = f32(g.app.height) * 0.5 - f32(p.racket_size) * 0.5
284
		i++
285
	}
286
	g.ball.pos.set(f32(g.app.width) * 0.5, f32(g.app.height) * 0.5)
287
	g.ball.vel.set(-8, -15)
288
	g.ball.acc.set(2.0, 1.0)
289
}
290

291
fn (mut g Game) update() {
292
	dt := g.app.dt
293
	mut b := unsafe { &g.ball }
294
	for mut p in g.players {
295
		p.update()
296
		// Keep rackets within the game area
297
		if p.pos.y <= 0 {
298
			p.pos.y = 1
299
		}
300
		if p.pos.y + p.racket_size >= g.app.height {
301
			p.pos.y = g.app.height - p.racket_size - 1
302
		}
303
		// Check ball collision
304
		// Player left side
305
		if p.pos.x < f32(g.app.width) * 0.5 {
306
			// Racket collision
307
			if b.pos.x <= p.pos.x + 1 {
308
				if b.pos.y >= p.pos.y && b.pos.y <= p.pos.y + p.racket_size {
309
					b.vel.x *= -1
310
				}
311
			}
312
			// Behind racket
313
			if b.pos.x < p.pos.x {
314
				g.players[1].score++
315
				g.new_round()
316
			}
317
		} else {
318
			// Player right side
319
			if b.pos.x >= p.pos.x - 1 {
320
				if b.pos.y >= p.pos.y && b.pos.y <= p.pos.y + p.racket_size {
321
					b.vel.x *= -1
322
				}
323
			}
324
			if b.pos.x > p.pos.x {
325
				g.players[0].score++
326
				g.new_round()
327
			}
328
		}
329
	}
330
	if b.pos.x <= 1 || b.pos.x >= g.app.width {
331
		b.vel.x *= -1
332
	}
333
	if b.pos.y <= 2 || b.pos.y >= g.app.height {
334
		b.vel.y *= -1
335
	}
336
	b.update(dt)
337
}
338

339
fn (mut g Game) quit() {
340
	if g.app.mode != .game {
341
		return
342
	}
343
	g.app.mode = .menu
344
}
345

346
fn (mut g Game) draw_big_digit(px f32, py f32, digit int) {
347
	// TODO: use draw_line or draw_point to fix tearing with non-monospaced terminal fonts
348
	mut gfx := g.app.tui
349
	x, y := int(px), int(py)
350
	match digit {
351
		0 {
352
			gfx.draw_text(x, y + 0, '█████')
353
			gfx.draw_text(x, y + 1, '█   █')
354
			gfx.draw_text(x, y + 2, '█   █')
355
			gfx.draw_text(x, y + 3, '█   █')
356
			gfx.draw_text(x, y + 4, '█████')
357
		}
358
		1 {
359
			gfx.draw_text(x + 3, y + 0, '█')
360
			gfx.draw_text(x + 3, y + 1, '█')
361
			gfx.draw_text(x + 3, y + 2, '█')
362
			gfx.draw_text(x + 3, y + 3, '█')
363
			gfx.draw_text(x + 3, y + 4, '█')
364
		}
365
		2 {
366
			gfx.draw_text(x, y + 0, '█████')
367
			gfx.draw_text(x, y + 1, '    █')
368
			gfx.draw_text(x, y + 2, '█████')
369
			gfx.draw_text(x, y + 3, '█')
370
			gfx.draw_text(x, y + 4, '█████')
371
		}
372
		3 {
373
			gfx.draw_text(x, y + 0, '█████')
374
			gfx.draw_text(x, y + 1, '   ██')
375
			gfx.draw_text(x, y + 2, ' ████')
376
			gfx.draw_text(x, y + 3, '   ██')
377
			gfx.draw_text(x, y + 4, '█████')
378
		}
379
		4 {
380
			gfx.draw_text(x, y + 0, '█   █')
381
			gfx.draw_text(x, y + 1, '█   █')
382
			gfx.draw_text(x, y + 2, '█████')
383
			gfx.draw_text(x, y + 3, '    █')
384
			gfx.draw_text(x, y + 4, '    █')
385
		}
386
		5 {
387
			gfx.draw_text(x, y + 0, '█████')
388
			gfx.draw_text(x, y + 1, '█')
389
			gfx.draw_text(x, y + 2, '█████')
390
			gfx.draw_text(x, y + 3, '    █')
391
			gfx.draw_text(x, y + 4, '█████')
392
		}
393
		6 {
394
			gfx.draw_text(x, y + 0, '█████')
395
			gfx.draw_text(x, y + 1, '█')
396
			gfx.draw_text(x, y + 2, '█████')
397
			gfx.draw_text(x, y + 3, '█   █')
398
			gfx.draw_text(x, y + 4, '█████')
399
		}
400
		7 {
401
			gfx.draw_text(x, y + 0, '█████')
402
			gfx.draw_text(x, y + 1, '    █')
403
			gfx.draw_text(x, y + 2, '    █')
404
			gfx.draw_text(x, y + 3, '    █')
405
			gfx.draw_text(x, y + 4, '    █')
406
		}
407
		8 {
408
			gfx.draw_text(x, y + 0, '█████')
409
			gfx.draw_text(x, y + 1, '█   █')
410
			gfx.draw_text(x, y + 2, '█████')
411
			gfx.draw_text(x, y + 3, '█   █')
412
			gfx.draw_text(x, y + 4, '█████')
413
		}
414
		9 {
415
			gfx.draw_text(x, y + 0, '█████')
416
			gfx.draw_text(x, y + 1, '█   █')
417
			gfx.draw_text(x, y + 2, '█████')
418
			gfx.draw_text(x, y + 3, '    █')
419
			gfx.draw_text(x, y + 4, '█████')
420
		}
421
		else {}
422
	}
423
}
424

425
fn (mut g Game) draw() {
426
	mut gfx := g.app.tui
427
	gfx.set_bg_color(white)
428
	// Border
429
	gfx.draw_empty_rect(1, 1, g.app.width, g.app.height)
430
	// Center line
431
	gfx.draw_dashed_line(int(f32(g.app.width) * 0.5), 0, int(f32(g.app.width) * 0.5),
432
		int(g.app.height))
433
	border := 1
434
	mut y, mut x := 0, 0
435
	for p in g.players {
436
		x = int(p.pos.x)
437
		y = int(p.pos.y)
438
		gfx.reset_bg_color()
439
		gfx.set_color(white)
440
		if x < f32(g.app.width) * 0.5 {
441
			g.draw_big_digit(f32(g.app.width) * 0.25, 3, p.score)
442
		} else {
443
			g.draw_big_digit(f32(g.app.width) * 0.75, 3, p.score)
444
		}
445
		gfx.reset_color()
446
		gfx.set_bg_color(white)
447
		// Racket
448
		gfx.draw_line(x, y + border, x, y + p.racket_size)
449
	}
450
	// Ball
451
	gfx.draw_point(int(g.ball.pos.x), int(g.ball.pos.y))
452
	// gfx.draw_text(22,2,'$g.ball.pos')
453
	gfx.reset_bg_color()
454
}
455

456
fn (mut g Game) free() {
457
	g.players.clear()
458
}
459

460
// TODO: Remove these wrapper functions when we can assign methods as callbacks
461
fn init(mut app App) {
462
	app.init()
463
}
464

465
fn frame(mut app App) {
466
	app.frame()
467
}
468

469
fn cleanup(mut app App) {
470
	unsafe {
471
		app.free()
472
	}
473
}
474

475
fn fail(error string) {
476
	eprintln(error)
477
}
478

479
fn event(e &ui.Event, mut app App) {
480
	app.event(e)
481
}
482

483
type InitFn = fn (voidptr)
484

485
type EventFn = fn (&ui.Event, voidptr)
486

487
type FrameFn = fn (voidptr)
488

489
type CleanupFn = fn (voidptr)
490

491
fn main() {
492
	mut app := &App{}
493
	app.tui = ui.init(
494
		user_data:      app
495
		init_fn:        InitFn(init)
496
		frame_fn:       FrameFn(frame)
497
		cleanup_fn:     CleanupFn(cleanup)
498
		event_fn:       EventFn(event)
499
		fail_fn:        fail
500
		capture_events: true
501
		hide_cursor:    true
502
		frame_rate:     60
503
	)
504
	app.tui.run()!
505
}
506

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.