├── .gitignore ├── beta.png ├── glfw3.dll ├── freetype.dll ├── RobotoMono-Regular.ttf ├── resources └── sounds │ ├── click.wav │ ├── enter.wav │ ├── error.wav │ ├── launch.wav │ ├── boop_wall.wav │ ├── destroyed.wav │ ├── boop_paddle.wav │ └── score_point.wav ├── .gitmodules ├── .github └── workflows │ └── main.yml ├── README.MD └── pong.v /.gitignore: -------------------------------------------------------------------------------- 1 | fns.txt 2 | lookup_pong.c 3 | *.exe 4 | tetris* -------------------------------------------------------------------------------- /beta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inxomnyaa/v-pong/HEAD/beta.png -------------------------------------------------------------------------------- /glfw3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inxomnyaa/v-pong/HEAD/glfw3.dll -------------------------------------------------------------------------------- /freetype.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inxomnyaa/v-pong/HEAD/freetype.dll -------------------------------------------------------------------------------- /RobotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inxomnyaa/v-pong/HEAD/RobotoMono-Regular.ttf -------------------------------------------------------------------------------- /resources/sounds/click.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inxomnyaa/v-pong/HEAD/resources/sounds/click.wav -------------------------------------------------------------------------------- /resources/sounds/enter.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inxomnyaa/v-pong/HEAD/resources/sounds/enter.wav -------------------------------------------------------------------------------- /resources/sounds/error.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inxomnyaa/v-pong/HEAD/resources/sounds/error.wav -------------------------------------------------------------------------------- /resources/sounds/launch.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inxomnyaa/v-pong/HEAD/resources/sounds/launch.wav -------------------------------------------------------------------------------- /resources/sounds/boop_wall.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inxomnyaa/v-pong/HEAD/resources/sounds/boop_wall.wav -------------------------------------------------------------------------------- /resources/sounds/destroyed.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inxomnyaa/v-pong/HEAD/resources/sounds/destroyed.wav -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "miniaudio"] 2 | path = miniaudio 3 | url = https://github.com/Larpon/v-miniaudio.git 4 | -------------------------------------------------------------------------------- /resources/sounds/boop_paddle.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inxomnyaa/v-pong/HEAD/resources/sounds/boop_paddle.wav -------------------------------------------------------------------------------- /resources/sounds/score_point.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inxomnyaa/v-pong/HEAD/resources/sounds/score_point.wav -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Checkout submodules 13 | shell: bash 14 | run: | 15 | auth_header="$(git config --local --get http.https://github.com/.extraheader)" 16 | git submodule sync --recursive 17 | git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 18 | - name: Build V Project 19 | uses: nzbr/vlang-action@v1 20 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # v-pong 2 | Pong written in V https://vlang.io/ 3 | ## Project 4 | I was just fiddling around with V and the ui, and managed to create this. 5 | 6 | Preview on Twitter: https://twitter.com/XenialDan/status/1203521657266200576 7 | ## Build 8 | - To build on Windows you need the dll files for freetype and glfw3 9 | - `v build pong.v` 10 | ## Run 11 | - `v run pong.v` 12 | ## Planned 13 | - Better UI 14 | - Better platform control 15 | - Better enemy (already improved) 16 | - Difficulty (enemy ball tracking speed) 17 | - Pause screen 18 | - End / Win / Loose 19 | - [x] Launch ball with space 20 | - Short boost with space 21 | - Cleanup 22 | - 2 Player mode 23 | - Key setup -------------------------------------------------------------------------------- /pong.v: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | import os 4 | import math 5 | import time 6 | import gx 7 | //import gl 8 | import gg 9 | import glfw 10 | import freetype 11 | import miniaudio as ma 12 | 13 | const ( 14 | Width = 800 //px windows 15 | Height = 500 //px window 16 | PaddleHeight = 10 //px 17 | PaddleWidth = 50 //px 18 | PaddleSpeedComputer = 5 //px/s 19 | PaddleSpeed = 5 //px/s 20 | PaddlePadding = 20 //px from top/bottom 21 | TickSpeed = 10 //ms refresh rate 22 | BallSize = 10 //px 23 | BallVelocity = 1 //px/s movement speed 24 | TextSize = 12 //px 25 | Debug = false //true or false, display more info 26 | ) 27 | 28 | const ( 29 | wav_boop_paddle = './resources/sounds/boop_paddle.wav' 30 | wav_boop_wall = './resources/sounds/boop_wall.wav' 31 | wav_click = './resources/sounds/click.wav' 32 | wav_destroyed = './resources/sounds/destroyed.wav' 33 | wav_enter = './resources/sounds/enter.wav' 34 | wav_error = './resources/sounds/error.wav' 35 | wav_launch = './resources/sounds/launch.wav' 36 | wav_score_point = './resources/sounds/score_point.wav' 37 | ) 38 | 39 | const ( 40 | text_cfg = gx.TextCfg{ 41 | align:gx.ALIGN_LEFT 42 | size:TextSize 43 | color:gx.Green 44 | } 45 | over_cfg = gx.TextCfg{ 46 | align:gx.ALIGN_LEFT 47 | size:TextSize 48 | color:gx.White 49 | } 50 | ) 51 | 52 | const ( 53 | BackgroundColor = gx.Black 54 | UIColor = gx.Green 55 | PaddleColor = gx.Green 56 | ) 57 | 58 | struct PaddleComputer { 59 | mut: 60 | x int 61 | score int 62 | dx f32 63 | } 64 | 65 | struct PaddlePlayer { 66 | mut: 67 | x int 68 | score int 69 | dx int 70 | } 71 | 72 | struct Ball { 73 | mut: 74 | x f32 75 | y f32 76 | dx f32 77 | dy f32 78 | speed f32 79 | collided bool 80 | } 81 | 82 | struct Game { 83 | mut: 84 | height int 85 | width int 86 | ball Ball 87 | player PaddlePlayer 88 | computer PaddleComputer 89 | sounds Sounds 90 | // gg context for drawing 91 | gg &gg.GG 92 | // ft context for font drawing 93 | ft &freetype.FreeType 94 | font_loaded bool 95 | } 96 | 97 | struct Sounds { 98 | mut: 99 | boop_paddle miniaudio.MiniAudio 100 | boop_wall miniaudio.MiniAudio 101 | click miniaudio.MiniAudio 102 | destroyed miniaudio.MiniAudio 103 | enter miniaudio.MiniAudio 104 | error miniaudio.MiniAudio 105 | launch miniaudio.MiniAudio 106 | score_point miniaudio.MiniAudio 107 | } 108 | 109 | fn main() { 110 | os.clear() 111 | glfw.init_glfw() 112 | //init game 113 | mut game := &Game{ 114 | gg: gg.new_context(gg.Cfg { 115 | width: Width 116 | height: Height 117 | use_ortho: true // This is needed for 2D drawing 118 | create_window: true 119 | window_title: 'V Pong' 120 | window_user_ptr: game 121 | }) 122 | ft: 0 123 | height: Height 124 | width: Width 125 | } 126 | game.gg.window.set_user_ptr(game) // TODO remove this when `window_user_ptr:` works 127 | game.init_game() 128 | game.sounds = Sounds{} 129 | game.sounds.init_sounds() 130 | println('Starting the game loop...') 131 | game.gg.window.onkeydown(key_down) 132 | go game.run() // Run the game loop in a new thread 133 | gg.clear(BackgroundColor) 134 | // Try to load font 135 | game.ft = freetype.new_context(gg.Cfg{ 136 | width: Width 137 | height: Height 138 | use_ortho: true 139 | font_size: 18 140 | scale: 2 141 | }) 142 | game.font_loaded = (game.ft != 0 ) 143 | for { 144 | gg.clear(BackgroundColor) 145 | game.draw_scene() 146 | game.gg.render() 147 | if game.gg.window.should_close() { 148 | game.gg.window.destroy() 149 | return 150 | } 151 | } 152 | } 153 | 154 | fn (g mut Game) init_game() { 155 | println('Init game...') 156 | //mut ball := &Ball{ 157 | g.player = PaddlePlayer{ 158 | x: g.width/2 159 | dx: 0 160 | } 161 | g.computer = PaddleComputer{ 162 | x: g.width/2 163 | dx: 0 164 | } 165 | g.ball = Ball{ 166 | //x: g.width / 2 167 | //y: g.height / 2 168 | x: (g.player.x + ((PaddleWidth/2)-(BallSize / 2))) 169 | y: (g.height - PaddlePadding - PaddleHeight - BallSize) 170 | //dx: BallVelocity 171 | //dy: -BallVelocity 172 | dx: 0 173 | dy: 0 174 | speed: BallVelocity 175 | collided: false 176 | } 177 | } 178 | 179 | fn (sound mut Sounds) init_sounds() { 180 | //load sounds 181 | println('Load sounds...') 182 | sound.boop_paddle = ma.from(wav_boop_paddle) 183 | sound.boop_wall = ma.from(wav_boop_wall) 184 | sound.click = ma.from(wav_click) 185 | sound.destroyed = ma.from(wav_destroyed) 186 | sound.enter = ma.from(wav_enter) 187 | sound.error = ma.from(wav_error) 188 | sound.launch = ma.from(wav_launch) 189 | sound.score_point = ma.from(wav_score_point) 190 | //sound_beep.free() 191 | } 192 | 193 | fn (g mut Game) reset() { 194 | println('Reset ball...') 195 | g.computer.x = g.width / 2 196 | g.computer.dx = 0 197 | g.player.x = g.width / 2 198 | g.player.dx = 0 199 | g.ball = Ball{ 200 | //x: g.width / 2 201 | //y: g.height / 2 202 | x: (g.player.x + ((PaddleWidth/2)-(BallSize / 2))) 203 | y: (g.height - PaddlePadding - PaddleHeight - BallSize) 204 | dx: 0 205 | dy: 0 206 | speed: BallVelocity 207 | collided: false 208 | } 209 | } 210 | 211 | //fn (g mut Game) draw_scene() { 212 | fn (g mut Game) draw_scene() { 213 | g.draw_ball() 214 | g.draw_paddle() 215 | //g.draw_field() 216 | g.draw_ui() 217 | if(Debug){ 218 | g.draw_debug() 219 | } 220 | } 221 | 222 | fn (g &Game) draw_ball() { 223 | color := UIColor 224 | g.gg.draw_rect(int(g.ball.x), int(g.ball.y), 225 | BallSize, BallSize, color) 226 | } 227 | 228 | fn (g &Game) draw_paddle() { 229 | color := PaddleColor 230 | g.gg.draw_rect(g.computer.x, PaddlePadding, 231 | PaddleWidth, PaddleHeight, color) 232 | g.gg.draw_rect(g.player.x, g.height - (PaddlePadding + PaddleHeight), 233 | PaddleWidth, PaddleHeight, color) 234 | } 235 | 236 | fn (g mut Game) run() { 237 | for { 238 | g.move_player() 239 | g.move_computer() 240 | g.check_ball_collision() 241 | g.move_ball() 242 | //ball.speed = ball.speed + SPEEDINCREASE 243 | //g.ball.dx *= ball.speed 244 | //g.ball.dy *= ball.speed 245 | //println('bdx $g.ball.dx bdy $g.ball.dy') 246 | // Refresh 247 | glfw.post_empty_event() // force window redraw 248 | time.sleep_ms(TickSpeed) 249 | } 250 | } 251 | 252 | fn (g mut Game) move_ball() { 253 | //if g.ball.y >= g.height - Height || g.ball.y <= 0 { 254 | //if g.ball.collided || g.ball.y >= (g.height - PaddlePadding){//TODO remove or when player added 255 | if g.ball.collided{ 256 | g.sounds.boop_paddle.play() 257 | println('ball bounce paddle') 258 | g.ball.speed *= 1.1 259 | g.ball.dx *= 1.1 260 | g.ball.dy *= 1.1 261 | g.ball.dy = -g.ball.dy 262 | g.ball.collided = false 263 | } 264 | //TODO else score 265 | //if g.ball.x >= g.width - Width || g.ball.x <= 0 { 266 | //bounce from side walls 267 | if g.ball.x >= g.width - BallSize || g.ball.x <= 0 { 268 | g.ball.dx = - g.ball.dx 269 | g.sounds.boop_wall.play() 270 | } 271 | if(g.ball.y > (g.height - BallSize)){ 272 | //Computer scored 273 | g.computer.score ++ 274 | g.sounds.destroyed.play() 275 | g.reset() 276 | return 277 | } 278 | if(g.ball.y < BallSize){ 279 | //Player scored 280 | g.player.score ++ 281 | g.sounds.score_point.play() 282 | g.reset() 283 | return 284 | } 285 | g.ball.x += g.ball.dx 286 | g.ball.y += g.ball.dy 287 | } 288 | 289 | fn (g mut Game) move_computer() { 290 | ball := g.ball 291 | if(ball.dx == 0) {return} 292 | fakecenter := g.computer.x + PaddleWidth/2 - BallSize/2 293 | //TODO target center of ball with center of target (done?) 294 | //if ball further left move left 295 | if ball.x < fakecenter { 296 | g.computer.dx = f32(math.max(g.ball.speed * -1.5, f32(-PaddleSpeedComputer))) 297 | } 298 | //if ball further right move right 299 | else if ball.x > fakecenter { 300 | g.computer.dx = f32(math.min(g.ball.speed * 1.5, f32(PaddleSpeedComputer))) 301 | } else{ 302 | //g.computer.dx = 0//TODO check this, makes paddle lag 303 | } 304 | if((g.computer.x + int(g.computer.dx)) < 0){ 305 | g.computer.dx = 0 306 | } 307 | if((g.computer.x + int(g.computer.dx)) > (g.width - PaddleWidth)){ 308 | g.computer.dx = 0 309 | } 310 | g.computer.x += int(g.computer.dx) 311 | } 312 | 313 | fn (g mut Game) move_player() { 314 | if((g.player.x + g.player.dx) < 0){ 315 | g.player.dx = 0 316 | } 317 | if((g.player.x + g.player.dx) > (g.width - PaddleWidth)){ 318 | g.player.dx = 0 319 | } 320 | if(g.ball.dy == 0){//TODO clean this up, temp to move ball when paused 321 | g.ball.dx = g.player.dx 322 | } 323 | g.player.x += int(g.player.dx) 324 | } 325 | 326 | fn (g mut Game) check_ball_collision() { 327 | ball := g.ball 328 | //computer := g.computer 329 | //player := g.player 330 | //top (computer) paddle 331 | //if(ball.y <= (PaddlePadding + PaddleHeight) && (ball.y + ball.dy) >= PaddlePadding){ 332 | if(ball.dy < 0 && ball.y <= (PaddlePadding + PaddleHeight) && (ball.y) >= PaddlePadding 333 | && ball.x > (g.computer.x - BallSize) && ball.x <= (g.computer.x + PaddleWidth)){ 334 | g.ball.collided = true 335 | println('ball collided computer') 336 | } 337 | //bottom (player) paddle 338 | //if(ball.y <= (g.height - PaddlePadding - PaddleHeight) && (ball.y + ball.dy) >= (g.height - PaddlePadding)){ 339 | //if(ball.y <= (g.height - PaddlePadding - PaddleHeight) && (ball.y) >= (g.height - PaddlePadding)){ 340 | if(ball.dy > 0 && (ball.y + BallSize) >= (g.height - PaddlePadding - PaddleHeight) && (ball.y + BallSize) <= (g.height - PaddlePadding) 341 | && ball.x > (g.player.x - BallSize) && ball.x <= (g.player.x + PaddleWidth)){ 342 | g.ball.collided = true 343 | println('ball collided player') 344 | } 345 | if(g.ball.collided){ 346 | println('BALL COLLIDED') 347 | } 348 | } 349 | 350 | fn (g mut Game) draw_debug() { 351 | if g.font_loaded { 352 | g.ft.draw_text(int(g.ball.x + BallSize), int(g.ball.y + BallSize), '$g.ball.x $g.ball.y', text_cfg) 353 | g.ft.draw_text(5, 2 + 5 + TextSize, '$g.computer.x', text_cfg) 354 | g.ft.draw_text(5, 2 + 2 + 5 + TextSize * 2, 'Computer dx: $g.computer.dx', text_cfg) 355 | mut text := '' 356 | if(g.ball.dy < 0){ text = 'Moving up'} 357 | else {text = 'Moving down'} 358 | g.ft.draw_text(5, 5 + TextSize * 3, text, text_cfg) 359 | g.ft.draw_text(5, 5 + TextSize * 4, 'Collided: $g.ball.collided', text_cfg) 360 | g.ft.draw_text(5, 5 + TextSize * 5, 'Player dx: $g.player.dx', text_cfg) 361 | g.ft.draw_text(5, g.height - 2 - 5 - TextSize - TextSize, '$g.player.x', text_cfg) 362 | } 363 | } 364 | 365 | fn (g mut Game) draw_ui() { 366 | if g.font_loaded { 367 | g.ft.draw_text(5, 2, 'Score: $g.computer.score', text_cfg) 368 | g.ft.draw_text(5, g.height - 2 - TextSize, 'Score: $g.player.score', text_cfg) 369 | } 370 | } 371 | 372 | // TODO: this exposes the unsafe C interface, clean up 373 | fn key_down(wnd voidptr, key, code, action, mods int) { 374 | //0 keyup 1 keydown 2 keyhold 375 | if action == 2 { 376 | return 377 | } 378 | // Fetch the game object stored in the user pointer 379 | mut game := &Game(glfw.get_window_user_pointer(wnd)) 380 | // global keys 381 | match key { 382 | //glfw.KEY_ESCAPE { 383 | // glfw.set_should_close(wnd, true) 384 | //} 385 | glfw.key_space { 386 | //if game.state == .running { 387 | // game.state = .paused 388 | //} else if game.state == .paused { 389 | // game.state = .running 390 | //} else if game.state == .gameover { 391 | // game.init_game() 392 | // game.state = .running 393 | //} 394 | if(action == 0 && game.ball.dy == 0){ 395 | game.sounds.launch.play() 396 | game.ball.dx = BallVelocity 397 | game.ball.dy = -BallVelocity 398 | } 399 | } 400 | else {} 401 | } 402 | 403 | //if game.state != .running { 404 | // return 405 | //} 406 | // keys while game is running 407 | match key { 408 | glfw.KeyLeft { 409 | if(action == 0){ 410 | game.player.dx = 0 411 | } 412 | if(action == 1){ 413 | game.player.dx = -PaddleSpeed 414 | } 415 | } 416 | glfw.KeyRight { 417 | if(action == 0){ 418 | game.player.dx = 0 419 | } 420 | if(action == 1){ 421 | game.player.dx = PaddleSpeed 422 | } 423 | } 424 | else { } 425 | } 426 | } 427 | --------------------------------------------------------------------------------