├── docs ├── robots.txt ├── favicon.ico ├── media │ ├── hit.f8648124.wav │ ├── score.63ce9ecd.wav │ ├── side.f37fcc5f.wav │ └── start.1f4848ab.wav ├── img │ └── icons │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── apple-touch-icon.png │ │ ├── mstile-150x150.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── msapplication-icon-144x144.png │ │ ├── android-chrome-maskable-192x192.png │ │ ├── android-chrome-maskable-512x512.png │ │ └── safari-pinned-tab.svg ├── css │ └── app.4dedb158.css ├── js │ ├── about.c058c680.js │ ├── about.c058c680.js.map │ ├── app.4b46cf16.js │ └── app.4b46cf16.js.map ├── manifest.json ├── 404.html ├── service-worker.js ├── precache-manifest.94bcd2149b8874d0d73d930b3affd641.js └── index.html ├── public ├── robots.txt ├── favicon.ico ├── img │ └── icons │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-150x150.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── msapplication-icon-144x144.png │ │ ├── android-chrome-maskable-192x192.png │ │ ├── android-chrome-maskable-512x512.png │ │ └── safari-pinned-tab.svg ├── 404.html └── index.html ├── .browserslistrc ├── cypress.json ├── jest.config.js ├── src ├── assets │ ├── hit.wav │ ├── logo.png │ ├── score.wav │ ├── side.wav │ └── start.wav ├── pong │ ├── movement.js │ ├── index.js │ ├── shaders │ │ ├── shader.vert │ │ └── shader.frag │ ├── player.js │ ├── ball.js │ ├── shader.js │ ├── pong-renderer.js │ └── pong.js ├── views │ ├── About.vue │ └── Home.vue ├── store │ └── index.js ├── main.js ├── App.vue ├── router │ └── index.js ├── registerServiceWorker.js └── components │ └── HelloWorld.vue ├── babel.config.js ├── Instructions └── INFOGR_P3_2020_Retake.pdf ├── .editorconfig ├── tests ├── e2e │ ├── .eslintrc.js │ ├── specs │ │ └── test.js │ ├── support │ │ ├── index.js │ │ └── commands.js │ └── plugins │ │ └── index.js └── unit │ └── example.spec.js ├── vue.config.js ├── .gitignore ├── .eslintrc.js ├── package.json ├── README.md └── README.txt /docs/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/favicon.ico -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@vue/cli-plugin-unit-jest' 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/hit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/src/assets/hit.wav -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/score.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/src/assets/score.wav -------------------------------------------------------------------------------- /src/assets/side.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/src/assets/side.wav -------------------------------------------------------------------------------- /src/assets/start.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/src/assets/start.wav -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /docs/media/hit.f8648124.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/media/hit.f8648124.wav -------------------------------------------------------------------------------- /docs/media/score.63ce9ecd.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/media/score.63ce9ecd.wav -------------------------------------------------------------------------------- /docs/media/side.f37fcc5f.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/media/side.f37fcc5f.wav -------------------------------------------------------------------------------- /docs/media/start.1f4848ab.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/media/start.1f4848ab.wav -------------------------------------------------------------------------------- /docs/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /docs/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /docs/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /src/pong/movement.js: -------------------------------------------------------------------------------- 1 | const Movement = { 2 | None: 0, 3 | Up: 1, 4 | Down: 2 5 | } 6 | 7 | export default Movement 8 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /Instructions/INFOGR_P3_2020_Retake.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/Instructions/INFOGR_P3_2020_Retake.pdf -------------------------------------------------------------------------------- /docs/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /docs/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /docs/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /docs/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /docs/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /docs/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /docs/img/icons/android-chrome-maskable-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/img/icons/android-chrome-maskable-192x192.png -------------------------------------------------------------------------------- /docs/img/icons/android-chrome-maskable-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/docs/img/icons/android-chrome-maskable-512x512.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /public/img/icons/android-chrome-maskable-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/public/img/icons/android-chrome-maskable-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-maskable-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/infogr-pong/master/public/img/icons/android-chrome-maskable-512x512.png -------------------------------------------------------------------------------- /src/pong/index.js: -------------------------------------------------------------------------------- 1 | import { Pong, GameStage } from './pong' 2 | import PongRenderer from './pong-renderer' 3 | 4 | export { Pong, PongRenderer, GameStage } 5 | -------------------------------------------------------------------------------- /tests/e2e/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | 'cypress' 4 | ], 5 | env: { 6 | mocha: true, 7 | 'cypress/globals': true 8 | }, 9 | rules: { 10 | strict: 'off' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /docs/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({ 7 | state: { 8 | }, 9 | mutations: { 10 | }, 11 | actions: { 12 | }, 13 | modules: { 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe('My First Test', () => { 4 | it('Visits the app root url', () => { 5 | cy.visit('/') 6 | cy.contains('h1', 'Welcome to Your Vue.js App') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import './registerServiceWorker' 4 | import router from './router' 5 | import store from './store' 6 | 7 | Vue.config.productionTip = false 8 | 9 | new Vue({ 10 | router, 11 | store, 12 | render: h => h(App) 13 | }).$mount('#app') 14 | -------------------------------------------------------------------------------- /docs/css/app.4dedb158.css: -------------------------------------------------------------------------------- 1 | body{background-color:#000}#app,body{padding:0;margin:0}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;height:100vh;width:100vw}#app,.home{display:grid}.home{align-items:center;justify-content:center}canvas{background-color:#fff;margin:0;padding:0} -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | publicPath: '/infogr-pong/', 5 | outputDir: path.resolve(__dirname, './docs'), 6 | configureWebpack: { 7 | module: { 8 | rules: [ 9 | { 10 | test: /\.vert$|\.frag$/i, 11 | use: 'raw-loader' 12 | } 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/pong/shaders/shader.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | attribute vec4 aVertexPosition; 4 | 5 | uniform mat4 uModelViewMatrix; 6 | uniform mat4 uProjectionMatrix; 7 | 8 | varying mediump vec2 screenPosition; 9 | 10 | void main() { 11 | screenPosition = aVertexPosition.xy; 12 | gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | pnpm-debug.log* 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /tests/unit/example.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import HelloWorld from '@/components/HelloWorld.vue' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('renders props.msg when passed', () => { 6 | const msg = 'new message' 7 | const wrapper = shallowMount(HelloWorld, { 8 | propsData: { msg } 9 | }) 10 | expect(wrapper.text()).toMatch(msg) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /docs/js/about.c058c680.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["about"],{f820:function(t,e,n){"use strict";n.r(e);var a=function(){var t=this,e=t.$createElement;t._self._c;return t._m(0)},s=[function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"about"},[n("h1",[t._v("This is an about page")])])}],u=n("2877"),c={},i=Object(u["a"])(c,a,s,!1,null,null,null);e["default"]=i.exports}}]); 2 | //# sourceMappingURL=about.c058c680.js.map -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 26 | -------------------------------------------------------------------------------- /docs/manifest.json: -------------------------------------------------------------------------------- 1 | {"name":"infogr-pong","short_name":"infogr-pong","theme_color":"#4DBA87","icons":[{"src":"./img/icons/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"./img/icons/android-chrome-512x512.png","sizes":"512x512","type":"image/png"},{"src":"./img/icons/android-chrome-maskable-192x192.png","sizes":"192x192","type":"image/png","purpose":"maskable"},{"src":"./img/icons/android-chrome-maskable-512x512.png","sizes":"512x512","type":"image/png","purpose":"maskable"}],"start_url":".","display":"standalone","background_color":"#000000"} -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | INFOGR Pong 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 |               18 |               19 |               20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | INFOGR Pong 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 |               18 |               19 |               20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | 'plugin:vue/essential', 8 | '@vue/standard' 9 | ], 10 | parserOptions: { 11 | parser: 'babel-eslint' 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' 16 | }, 17 | overrides: [ 18 | { 19 | files: [ 20 | '**/__tests__/*.{j,t}s?(x)', 21 | '**/tests/unit/**/*.spec.{j,t}s?(x)' 22 | ], 23 | env: { 24 | jest: true 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/pong/player.js: -------------------------------------------------------------------------------- 1 | import Movement from './movement' 2 | 3 | export default class Player { 4 | width 5 | height 6 | x 7 | y 8 | direction 9 | score 10 | speed 11 | 12 | constructor (x) { 13 | this.width = 0.04 14 | this.height = 0.3 15 | this.x = x 16 | this.y = 0 17 | this.speed = 0.02 18 | this.direction = Movement.None 19 | this.score = 0 20 | } 21 | 22 | left () { 23 | return this.x - this.width / 2 24 | } 25 | 26 | right () { 27 | return this.x + this.width / 2 28 | } 29 | 30 | top () { 31 | return this.y + this.height / 2 32 | } 33 | 34 | bottom () { 35 | return this.y - this.height / 2 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import Home from '../views/Home.vue' 4 | 5 | Vue.use(VueRouter) 6 | 7 | const routes = [ 8 | { 9 | path: '/', 10 | name: 'Home', 11 | component: Home 12 | }, 13 | { 14 | path: '/about', 15 | name: 'About', 16 | // route level code-splitting 17 | // this generates a separate chunk (about.[hash].js) for this route 18 | // which is lazy-loaded when the route is visited. 19 | component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') 20 | } 21 | ] 22 | 23 | const router = new VueRouter({ 24 | mode: 'history', 25 | base: process.env.BASE_URL, 26 | routes 27 | }) 28 | 29 | export default router 30 | -------------------------------------------------------------------------------- /tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/e2e/plugins/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable arrow-body-style */ 2 | // https://docs.cypress.io/guides/guides/plugins-guide.html 3 | 4 | // if you need a custom webpack configuration you can uncomment the following import 5 | // and then use the `file:preprocessor` event 6 | // as explained in the cypress docs 7 | // https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples 8 | 9 | // /* eslint-disable import/no-extraneous-dependencies, global-require */ 10 | // const webpack = require('@cypress/webpack-preprocessor') 11 | 12 | module.exports = (on, config) => { 13 | // on('file:preprocessor', webpack({ 14 | // webpackOptions: require('@vue/cli-service/webpack.config'), 15 | // watchOptions: {} 16 | // })) 17 | 18 | return Object.assign({}, config, { 19 | fixturesFolder: 'tests/e2e/fixtures', 20 | integrationFolder: 'tests/e2e/specs', 21 | screenshotsFolder: 'tests/e2e/screenshots', 22 | videosFolder: 'tests/e2e/videos', 23 | supportFile: 'tests/e2e/support/index.js' 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from 'register-service-worker' 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready () { 8 | console.log( 9 | 'App is being served from cache by a service worker.\n' + 10 | 'For more details, visit https://goo.gl/AFskqB' 11 | ) 12 | }, 13 | registered () { 14 | console.log('Service worker has been registered.') 15 | }, 16 | cached () { 17 | console.log('Content has been cached for offline use.') 18 | }, 19 | updatefound () { 20 | console.log('New content is downloading.') 21 | }, 22 | updated () { 23 | console.log('New content is available; please refresh.') 24 | }, 25 | offline () { 26 | console.log('No internet connection found. App is running in offline mode.') 27 | }, 28 | error (error) { 29 | console.error('Error during service worker registration:', error) 30 | } 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /src/pong/ball.js: -------------------------------------------------------------------------------- 1 | export default class Ball { 2 | radius 3 | x 4 | y 5 | direction 6 | oldPositions = [] 7 | speed 8 | acceleration 9 | 10 | startDirections = [ 11 | { x: 0.01, y: 0.01 }, 12 | { x: 0.01, y: -0.01 }, 13 | { x: -0.01, y: 0.01 }, 14 | { x: -0.01, y: -0.01 } 15 | ] 16 | 17 | setLocation (x, y) { 18 | this.oldPositions.push(new Float32Array([x, y])) 19 | if (this.oldPositions.length > 30) { 20 | this.oldPositions.shift() 21 | } 22 | this.x = x 23 | this.y = y 24 | } 25 | 26 | constructor () { 27 | this.radius = 0.01 28 | this.x = 0 29 | this.y = 0 30 | this.direction = this.startDirections[Math.floor(Math.random() * this.startDirections.length)] 31 | this.speed = 0.01 32 | this.acceleration = 1.1 33 | 34 | for (var i = 0; i < 30; i++) { 35 | this.oldPositions[i] = new Float32Array([0, 0]) 36 | } 37 | } 38 | 39 | left () { 40 | return this.x - this.radius 41 | } 42 | 43 | right () { 44 | return this.x + this.radius 45 | } 46 | 47 | top () { 48 | return this.y + this.radius 49 | } 50 | 51 | bottom () { 52 | return this.y - this.radius 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); 15 | 16 | importScripts( 17 | "/infogr-pong/precache-manifest.94bcd2149b8874d0d73d930b3affd641.js" 18 | ); 19 | 20 | workbox.core.setCacheNameDetails({prefix: "infogr-pong"}); 21 | 22 | self.addEventListener('message', (event) => { 23 | if (event.data && event.data.type === 'SKIP_WAITING') { 24 | self.skipWaiting(); 25 | } 26 | }); 27 | 28 | /** 29 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 30 | * requests for URLs in the manifest. 31 | * See https://goo.gl/S9QRab 32 | */ 33 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 34 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 35 | -------------------------------------------------------------------------------- /docs/js/about.c058c680.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./src/views/About.vue?cdaf","webpack:///./src/views/About.vue"],"names":["render","_vm","this","_h","$createElement","_self","_c","_m","staticRenderFns","staticClass","_v","script","component"],"mappings":"8GAAA,IAAIA,EAAS,WAAa,IAAIC,EAAIC,KAASC,EAAGF,EAAIG,eAAsBH,EAAII,MAAMC,GAAO,OAAOL,EAAIM,GAAG,IACnGC,EAAkB,CAAC,WAAa,IAAIP,EAAIC,KAASC,EAAGF,EAAIG,eAAmBE,EAAGL,EAAII,MAAMC,IAAIH,EAAG,OAAOG,EAAG,MAAM,CAACG,YAAY,SAAS,CAACH,EAAG,KAAK,CAACL,EAAIS,GAAG,+B,YCAtJC,EAAS,GAKTC,EAAY,eACdD,EACAX,EACAQ,GACA,EACA,KACA,KACA,MAIa,aAAAI,E","file":"js/about.c058c680.js","sourcesContent":["var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _vm._m(0)}\nvar staticRenderFns = [function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"about\"},[_c('h1',[_vm._v(\"This is an about page\")])])}]\n\nexport { render, staticRenderFns }","import { render, staticRenderFns } from \"./About.vue?vue&type=template&id=1ae8a7be&\"\nvar script = {}\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports"],"sourceRoot":""} -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 41 | 42 | 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "infogr-pong", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "test:unit": "vue-cli-service test:unit", 9 | "test:e2e": "vue-cli-service test:e2e", 10 | "lint": "vue-cli-service lint" 11 | }, 12 | "dependencies": { 13 | "core-js": "^3.6.5", 14 | "register-service-worker": "^1.7.1", 15 | "vue": "^2.6.11", 16 | "vue-router": "^3.2.0", 17 | "vuex": "^3.4.0" 18 | }, 19 | "devDependencies": { 20 | "@vue/cli-plugin-babel": "~4.4.0", 21 | "@vue/cli-plugin-e2e-cypress": "~4.4.0", 22 | "@vue/cli-plugin-eslint": "~4.4.0", 23 | "@vue/cli-plugin-pwa": "~4.4.0", 24 | "@vue/cli-plugin-router": "~4.4.0", 25 | "@vue/cli-plugin-unit-jest": "~4.4.0", 26 | "@vue/cli-plugin-vuex": "~4.4.0", 27 | "@vue/cli-service": "~4.4.0", 28 | "@vue/eslint-config-standard": "^5.1.2", 29 | "@vue/test-utils": "^1.0.3", 30 | "babel-eslint": "^10.1.0", 31 | "eslint": "^6.7.2", 32 | "eslint-plugin-import": "^2.20.2", 33 | "eslint-plugin-node": "^11.1.0", 34 | "eslint-plugin-promise": "^4.2.1", 35 | "eslint-plugin-standard": "^4.0.0", 36 | "eslint-plugin-vue": "^6.2.2", 37 | "gl-matrix": "^3.3.0", 38 | "raw-loader": "^4.0.1", 39 | "sass": "^1.26.5", 40 | "sass-loader": "^8.0.2", 41 | "vue-template-compiler": "^2.6.11" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /docs/precache-manifest.94bcd2149b8874d0d73d930b3affd641.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = (self.__precacheManifest || []).concat([ 2 | { 3 | "revision": "d5085e11daa4d890913fbdb319257c7e", 4 | "url": "/infogr-pong/404.html" 5 | }, 6 | { 7 | "revision": "f04aff55e9b4797b9f90", 8 | "url": "/infogr-pong/css/app.4dedb158.css" 9 | }, 10 | { 11 | "revision": "1b685c29570444d1772f80ff36fbaade", 12 | "url": "/infogr-pong/index.html" 13 | }, 14 | { 15 | "revision": "792387564e323932678d", 16 | "url": "/infogr-pong/js/about.c058c680.js" 17 | }, 18 | { 19 | "revision": "f04aff55e9b4797b9f90", 20 | "url": "/infogr-pong/js/app.4b46cf16.js" 21 | }, 22 | { 23 | "revision": "af44d2a93ec7d451b576", 24 | "url": "/infogr-pong/js/chunk-vendors.7d9f1d29.js" 25 | }, 26 | { 27 | "revision": "124e9e43b4163fb40b51b6be966f8d17", 28 | "url": "/infogr-pong/manifest.json" 29 | }, 30 | { 31 | "revision": "f8648124be0a914e9984facdf89dcbfa", 32 | "url": "/infogr-pong/media/hit.f8648124.wav" 33 | }, 34 | { 35 | "revision": "63ce9ecdb52b2896bd16bd8d26b07e36", 36 | "url": "/infogr-pong/media/score.63ce9ecd.wav" 37 | }, 38 | { 39 | "revision": "f37fcc5fe035b88645f0097d4ca6fcd6", 40 | "url": "/infogr-pong/media/side.f37fcc5f.wav" 41 | }, 42 | { 43 | "revision": "1f4848ab75563b62b1e273c319c8124c", 44 | "url": "/infogr-pong/media/start.1f4848ab.wav" 45 | }, 46 | { 47 | "revision": "b6216d61c03e6ce0c9aea6ca7808f7ca", 48 | "url": "/infogr-pong/robots.txt" 49 | } 50 | ]); -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | infogr-pong
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # infogr-pong 2 | 3 | Authors: 4 | - Cor Pruijs (6595154) 5 | - Karel Kubat (6913466) 6 | 7 | ## Work division 8 | 9 | We pair programmed the entire application together. Cor did produce the actual sound files, and 10 | can be considered our lead designer. 11 | 12 | ## Features 13 | 14 | - Pong 15 | - Scoring 16 | - Character rendering 17 | - Score rendering 18 | - Ball tail 19 | - Randomized start angle 20 | - Ball speed up 21 | - Interactive play button (pointer events) 22 | - Start screen / Win screen 23 | - WebGL 24 | - VueJS app + hosting 25 | - VHS effect 26 | - Sound effects 27 | - Screen curvature 28 | 29 | ## Running the game 30 | Our entire game can be viewed [online](https://cor.github.io/infogr-pong)! 31 | Please view it in an updated version of Google Chrome or FireFox. 32 | 33 | ### Controls 34 | - Click the triangular play button to start 35 | - W/S for Player 1 movement 36 | - UpArrow/DownArrow for Player 2 movement 37 | 38 | ## Sources 39 | 40 | StackOverflow sources are included in the actual code. We used snippets to determine mouse locations on the 41 | HTML canvas, and [wessles](https://github.com/wessles/GLSL-CRT/blob/master/shader.frag) implementation of screen curvatures. 42 | 43 | The algorithm used to determine the angle that the ball makes when hitting the paddle is sourced from [Cor's previous 44 | Pong implementation](https://github.com/cor/Ping) 45 | 46 | Finally, we used official OpenGL and WebGL tutorials to create the app, 47 | and previous work by us on the [raytracer assignment](https://github.com/cor/infgr-ratyracer) as well. 48 | 49 | ## Project setup 50 | 51 | This project requires a modern JavaScript environment, most likely [npm](https://www.npmjs.com/) and [node](https://nodejs.org/en/). 52 | 53 | ``` 54 | npm install 55 | ``` 56 | 57 | ### Compiles and hot-reloads for development 58 | ``` 59 | npm run serve 60 | ``` 61 | 62 | ### Compiles and minifies for production 63 | ``` 64 | npm run build 65 | ``` 66 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | # infogr-pong 2 | 3 | Authors: 4 | - Cor Pruijs (6595154) 5 | - Karel Kubat (6913466) 6 | 7 | ## Work division 8 | 9 | We pair programmed the entire application together. Cor did produce the actual sound files, and 10 | can be considered our lead designer. 11 | 12 | ## Features 13 | 14 | - Pong 15 | - Scoring 16 | - Character rendering 17 | - Score rendering 18 | - Ball tail 19 | - Randomized start angle 20 | - Ball speed up 21 | - Interactive play button (pointer events) 22 | - Start screen / Win screen 23 | - WebGL 24 | - VueJS app + hosting 25 | - VHS effect 26 | - Sound effects 27 | - Screen curvature 28 | 29 | ## Running the game 30 | Our entire game can be viewed [online](https://cor.github.io/infogr-pong)! 31 | Please view it in an updated version of Google Chrome or FireFox. 32 | 33 | ### Controls 34 | - Click the triangular play button to start 35 | - W/S for Player 1 movement 36 | - UpArrow/DownArrow for Player 2 movement 37 | 38 | ## Sources 39 | 40 | StackOverflow sources are included in the actual code. We used snippets to determine mouse locations on the 41 | HTML canvas, and [wessles](https://github.com/wessles/GLSL-CRT/blob/master/shader.frag) implementation of screen curvatures. 42 | 43 | The algorithm used to determine the angle that the ball makes when hitting the paddle is sourced from [Cor's previous 44 | Pong implementation](https://github.com/cor/Ping) 45 | 46 | Finally, we used official OpenGL and WebGL tutorials to create the app, 47 | and previous work by us on the [raytracer assignment](https://github.com/cor/infgr-ratyracer) as well. 48 | 49 | ## Project setup 50 | 51 | This project requires a modern JavaScript environment, most likely [npm](https://www.npmjs.com/) and [node](https://nodejs.org/en/). 52 | 53 | ``` 54 | npm install 55 | ``` 56 | 57 | ### Compiles and hot-reloads for development 58 | ``` 59 | npm run serve 60 | ``` 61 | 62 | ### Compiles and minifies for production 63 | ``` 64 | npm run build 65 | ``` 66 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 46 | 47 | 48 | 64 | -------------------------------------------------------------------------------- /src/pong/shader.js: -------------------------------------------------------------------------------- 1 | export default class Shader { 2 | gl 3 | program 4 | 5 | // constructor (gl, vsSource, fsSource, sourceVars) { 6 | constructor (gl, vsSource, fsSource) { 7 | this.gl = gl 8 | 9 | // preprocess sources 10 | // vsSource = this.preprocessSource(vsSource, sourceVars) 11 | // fsSource = this.preprocessSource(fsSource, sourceVars) 12 | 13 | this.program = this.initShaderProgram(gl, vsSource, fsSource) 14 | } 15 | 16 | // 17 | // Initialize a shader program, so WebGL knows how to draw our data 18 | // 19 | initShaderProgram (gl, vsSource, fsSource) { 20 | const vertexShader = this.loadShader(gl, gl.VERTEX_SHADER, vsSource) 21 | const fragmentShader = this.loadShader(gl, gl.FRAGMENT_SHADER, fsSource) 22 | 23 | // Create the shader program 24 | const shaderProgram = gl.createProgram() 25 | gl.attachShader(shaderProgram, vertexShader) 26 | gl.attachShader(shaderProgram, fragmentShader) 27 | gl.linkProgram(shaderProgram) 28 | 29 | // If creating the shader program failed, alert 30 | if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { 31 | alert(`Unable to initialize the shader program: ${gl.getProgramInfoLog(shaderProgram)}`) 32 | return null 33 | } 34 | 35 | return shaderProgram 36 | } 37 | 38 | // 39 | // preprocesses our shader source files 40 | // it replaces variables in the format 42//$VARIABLE_NAME$// with the VARIABLE_NAME 41 | // item from the sourceVars object 42 | // 43 | preprocessSource (source, sourceVars) { 44 | const sourceVarPattern = /42\/\/\$(.*)\$\/\//g 45 | return source.replace(sourceVarPattern, (_, varName) => sourceVars[varName]) 46 | } 47 | 48 | // 49 | // creates a shader of the given type, uploads the source and 50 | // compiles it. 51 | // 52 | loadShader (gl, type, source) { 53 | const shader = gl.createShader(type) 54 | 55 | // Send the source to the shader object 56 | gl.shaderSource(shader, source) 57 | 58 | // Compile the shader program 59 | gl.compileShader(shader) 60 | 61 | // See if it compiled successfully 62 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 63 | console.error(`An error occurred compiling the shaders:\n${gl.getShaderInfoLog(shader)}`) 64 | gl.deleteShader(shader) 65 | return null 66 | } 67 | 68 | return shader 69 | } 70 | 71 | use () { 72 | this.gl.useProgram(this.program) 73 | } 74 | 75 | setUniform1f (name, value) { 76 | this.gl.uniform1f(this.getUniformLocation(name), value) 77 | } 78 | 79 | setUniformMatrix4fv (name, value) { 80 | this.gl.uniformMatrix4fv(this.getUniformLocation(name), false, value) 81 | } 82 | 83 | setUniform2fv (name, value) { 84 | this.gl.uniform2fv(this.getUniformLocation(name), value) 85 | } 86 | 87 | setUniform3fv (name, value) { 88 | this.gl.uniform3fv(this.getUniformLocation(name), value) 89 | } 90 | 91 | getUniformLocation (name) { 92 | // TODO: Cache uniform locations 93 | return this.gl.getUniformLocation(this.program, name) 94 | } 95 | 96 | getAttribLocation (name) { 97 | // TODO: Cache attrib locations 98 | return this.gl.getAttribLocation(this.program, name) 99 | } 100 | 101 | delete () { 102 | this.gl.deleteProgram(this.program) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/pong/pong-renderer.js: -------------------------------------------------------------------------------- 1 | import { mat4 } from 'gl-matrix' 2 | import Shader from './shader' 3 | 4 | import fsSource from './shaders/shader.frag' 5 | import vsSource from './shaders/shader.vert' 6 | 7 | export default class PongRenderer { 8 | gl 9 | shader 10 | buffers 11 | 12 | // constructor (gl, shaderSourceVars) { 13 | constructor (gl) { 14 | this.gl = gl 15 | this.shader = new Shader(gl, vsSource, fsSource) 16 | this.buffers = this.initBuffers() 17 | 18 | this.shader.use() 19 | this.setDefaultPositions() 20 | } 21 | 22 | recompileShader (shaderSourceVars) { 23 | this.shader.delete() 24 | this.shader = new Shader(this.gl, vsSource, fsSource, shaderSourceVars) 25 | } 26 | 27 | initBuffers () { 28 | // Create a buffer for the square's positions. 29 | 30 | const positionBuffer = this.gl.createBuffer() 31 | 32 | // Select the positionBuffer as the one to apply buffer 33 | // operations to from here out. 34 | 35 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, positionBuffer) 36 | 37 | // Now create an array of positions for the square. 38 | 39 | const positions = [ 40 | -1.0, 1.0, 41 | 1.0, 1.0, 42 | -1.0, -1.0, 43 | 1.0, -1.0 44 | ] 45 | 46 | // Now pass the list of positions into WebGL to build the 47 | // shape. We do this by creating a Float32Array from the 48 | // JavaScript array, then use it to fill the current buffer. 49 | this.gl.bufferData(this.gl.ARRAY_BUFFER, 50 | new Float32Array(positions), 51 | this.gl.STATIC_DRAW) 52 | 53 | return { 54 | position: positionBuffer 55 | } 56 | } 57 | 58 | draw () { 59 | this.gl.clearColor(0.0, 0.0, 0.0, 1.0) // Clear to black, fully opaque 60 | this.gl.clear(this.gl.COLOR_BUFFER_BIT) 61 | this.gl.clearDepth(1.0) // Clear everything 62 | this.gl.enable(this.gl.DEPTH_TEST) // Enable depth testing 63 | this.gl.depthFunc(this.gl.LEQUAL) // Near things obscure far things 64 | 65 | // Clear the canvas before we start drawing on it. 66 | 67 | this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT) 68 | 69 | // Create a perspective matrix, a special matrix that is 70 | // used to simulate the distortion of perspective in a camera. 71 | const fieldOfView = 29.8 * Math.PI / 180 // in radians 72 | const aspect = this.gl.canvas.clientWidth / this.gl.canvas.clientHeight 73 | const zNear = 0.1 74 | const zFar = 100.0 75 | const projectionMatrix = mat4.create() 76 | 77 | // gl-matrix.js always has the first argument as the destination to receive the result. 78 | mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar) 79 | 80 | // Set the drawing position to the "identity" point, which is 81 | // the center of the scene. 82 | const modelViewMatrix = mat4.create() 83 | 84 | // Now move the drawing position a bit to where we want to 85 | // start drawing the square. 86 | mat4.translate(modelViewMatrix, // destination matrix 87 | modelViewMatrix, // matrix to translate 88 | [0.0, 0.0, -2.5]) // amount to translate 89 | 90 | // Tell WebGL how to pull out the positions from the position 91 | // buffer into the vertexPosition attribute. 92 | const vertexPositionLocation = this.shader.getAttribLocation('aVertexPosition') 93 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.position) 94 | this.gl.vertexAttribPointer(vertexPositionLocation, 2, this.gl.FLOAT, false, 0, 0) 95 | this.gl.enableVertexAttribArray(vertexPositionLocation) 96 | 97 | // Tell WebGL to use our program when drawing 98 | this.shader.use() 99 | 100 | // Set the shader uniforms 101 | this.shader.setUniformMatrix4fv('uProjectionMatrix', projectionMatrix) 102 | this.shader.setUniformMatrix4fv('uModelViewMatrix', modelViewMatrix) 103 | { 104 | const offset = 0 105 | const vertexCount = 4 106 | this.gl.drawArrays(this.gl.TRIANGLE_STRIP, offset, vertexCount) 107 | } 108 | } 109 | 110 | setOldPositions (oldPositions) { 111 | for (const [i, pos] of oldPositions.entries()) { 112 | this.shader.setUniform2fv(`oldPositions[${i}]`, pos) 113 | } 114 | } 115 | 116 | setTime (time) { 117 | this.shader.setUniform1f('time', time) 118 | } 119 | 120 | setGameStage (gameStage) { 121 | this.shader.setUniform1f('gameStage', gameStage) 122 | } 123 | 124 | setDefaultPositions () { 125 | this.setLeftPaddlePosition(-0.9, 0) 126 | this.setRightPaddlePosition(0.9, 0) 127 | this.setBallPosition(0, 0) 128 | } 129 | 130 | setLeftPaddlePosition (x, y) { 131 | this.shader.setUniform2fv('leftPaddlePosition', new Float32Array([x, y])) 132 | } 133 | 134 | setRightPaddlePosition (x, y) { 135 | this.shader.setUniform2fv('rightPaddlePosition', new Float32Array([x, y])) 136 | } 137 | 138 | setBallPosition (x, y) { 139 | this.shader.setUniform2fv('ballPosition', new Float32Array([x, y])) 140 | } 141 | 142 | setScore (x, y) { 143 | this.shader.setUniform2fv('score', new Float32Array([x, y])) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/pong/pong.js: -------------------------------------------------------------------------------- 1 | import Player from './player' 2 | import Movement from './movement' 3 | import Ball from './ball' 4 | 5 | const ScoreAudio = new Audio(require('../assets/score.wav')) 6 | const PlopAudio = new Audio(require('../assets/hit.wav')) 7 | const StartAudio = new Audio(require('../assets/start.wav')) 8 | const SideAudio = new Audio(require('../assets/side.wav')) 9 | 10 | export const GameStage = { 11 | Welcome: 0.0, 12 | Playing: 1.0, 13 | P1Win: 2.0, 14 | P2Win: 3.0 15 | } 16 | 17 | export class Pong { 18 | countdown 19 | stage 20 | canvas 21 | center 22 | renderer 23 | state 24 | maxY 25 | time 26 | 27 | constructor (renderer, canvas, stage) { 28 | this.countdown = 60 29 | this.time = 0 30 | if (typeof stage !== 'undefined') { 31 | this.stage = stage 32 | } else { 33 | this.stage = GameStage.Playing 34 | } 35 | 36 | this.canvas = canvas 37 | this.renderer = renderer 38 | this.maxY = 0.65 39 | this.state = new State() 40 | 41 | this.center = { 42 | x: canvas.width / 2, 43 | y: canvas.height / 2 44 | } 45 | } 46 | 47 | tick () { 48 | this.time++ 49 | this.renderer.setTime(this.time) 50 | if (this.stage === GameStage.Playing) { 51 | this.state = this.transition(this.state) 52 | this.syncState() 53 | } else { 54 | this.renderer.setGameStage(this.stage) 55 | } 56 | this.draw() 57 | } 58 | 59 | transition (state) { 60 | this.countdown-- 61 | this.stage = this.state.currentStage() 62 | this.updatePlayerState(state.P1) 63 | this.updatePlayerState(state.P2) 64 | 65 | if (this.countdown > 0) { 66 | return state 67 | } 68 | 69 | if (this.countdown === 0) { 70 | StartAudio.play() 71 | } 72 | 73 | this.updateBallState(state.ball) 74 | 75 | if (state.ball.x <= -1) { 76 | const newState = new State() 77 | newState.P1 = state.P1 78 | newState.P2 = state.P2 79 | newState.P2.score++ 80 | ScoreAudio.play() 81 | this.countdown = 60 82 | return newState 83 | } else if (state.ball.x >= 1) { 84 | const newState = new State() 85 | newState.P1 = state.P1 86 | newState.P2 = state.P2 87 | newState.P1.score++ 88 | this.countdown = 60 89 | ScoreAudio.play() 90 | return newState 91 | } 92 | 93 | if (state.terminating) { 94 | return state 95 | } 96 | 97 | const maxBounceAngle = 5 * Math.PI / 12 98 | 99 | // Paddle collision 100 | if (state.ball.left() < state.P1.right()) { 101 | if ((state.ball.bottom() < state.P1.top() && state.ball.bottom > state.P1.bottom()) || 102 | (state.ball.top() < state.P1.top() && state.ball.top() > state.P1.bottom())) { 103 | PlopAudio.play() 104 | const distanceToNormal = state.P1.y - state.ball.y 105 | const normalizedDistanceToNormal = distanceToNormal / (state.P1.height / 2) 106 | const bounceAngle = normalizedDistanceToNormal * maxBounceAngle 107 | 108 | state.ball.speed *= state.ball.acceleration 109 | state.ball.direction.x = state.ball.speed * Math.cos(bounceAngle) 110 | state.ball.direction.y = -state.ball.speed * Math.sin(bounceAngle) 111 | } else { 112 | state.terminating = true 113 | } 114 | } 115 | 116 | if (state.ball.right() > state.P2.left()) { 117 | if ((state.ball.bottom() < state.P2.top() && state.ball.bottom > state.P2.bottom()) || 118 | (state.ball.top() < state.P2.top() && state.ball.top() > state.P2.bottom())) { 119 | PlopAudio.play() 120 | const distanceToNormal = state.P2.y - state.ball.y 121 | const normalizedDistanceToNormal = distanceToNormal / (state.P2.height / 2) 122 | const bounceAngle = normalizedDistanceToNormal * maxBounceAngle 123 | 124 | state.ball.speed *= state.ball.acceleration 125 | 126 | state.ball.direction.x = state.ball.speed * -Math.cos(bounceAngle) 127 | state.ball.direction.y = state.ball.speed * -Math.sin(bounceAngle) 128 | } else { 129 | state.terminating = true 130 | } 131 | } 132 | 133 | // if ball hits paddle, compute new ball direction. 134 | return this.state 135 | } 136 | 137 | updateBallState (ball) { 138 | ball.setLocation(ball.x + ball.direction.x, ball.y + ball.direction.y) 139 | if (Math.abs(ball.y) > this.maxY) { 140 | SideAudio.play() 141 | ball.direction.y *= -1 142 | } 143 | } 144 | 145 | updatePlayerState (P) { 146 | switch (P.direction) { 147 | case Movement.Up: 148 | P.y = Math.min(P.y + P.speed, this.maxY - P.height / 2) 149 | break 150 | case Movement.Down: 151 | P.y = Math.max(P.y - P.speed, -this.maxY + P.height / 2) 152 | break 153 | } 154 | } 155 | 156 | syncState () { 157 | this.renderer.setOldPositions(this.state.ball.oldPositions) 158 | this.renderer.setGameStage(this.stage) 159 | this.renderer.setLeftPaddlePosition(this.state.P1.x, this.state.P1.y) 160 | this.renderer.setRightPaddlePosition(this.state.P2.x, this.state.P2.y) 161 | this.renderer.setBallPosition(this.state.ball.x, this.state.ball.y) 162 | this.renderer.setScore(this.state.P1.score, this.state.P2.score) 163 | } 164 | 165 | draw () { 166 | this.renderer.draw() 167 | } 168 | 169 | addEventListeners () { 170 | this.canvas.addEventListener('mousedown', (e) => { 171 | const hitboxSize = 50 172 | const click = getCursorPosition(this.canvas, e) 173 | 174 | if (Math.abs(this.center.x - click.x) < hitboxSize && 175 | Math.abs(this.center.y - click.y) < hitboxSize && 176 | this.stage !== GameStage.Playing) { 177 | this.state = new State() 178 | this.stage = GameStage.Playing 179 | } 180 | }) 181 | 182 | document.addEventListener('keydown', (k) => { 183 | switch (k.code) { 184 | case 'ArrowUp': 185 | this.state.P2.direction = Movement.Up 186 | break 187 | case 'ArrowDown': 188 | this.state.P2.direction = Movement.Down 189 | break 190 | case 'KeyW': 191 | this.state.P1.direction = Movement.Up 192 | break 193 | case 'KeyS': 194 | this.state.P1.direction = Movement.Down 195 | break 196 | } 197 | }) 198 | 199 | document.addEventListener('keyup', (k) => { 200 | switch (k.code) { 201 | case 'ArrowUp': 202 | if (this.state.P2.direction === Movement.Up) { 203 | this.state.P2.direction = Movement.None 204 | } 205 | break 206 | case 'ArrowDown': 207 | if (this.state.P2.direction === Movement.Down) { 208 | this.state.P2.direction = Movement.None 209 | } 210 | break 211 | case 'KeyW': 212 | if (this.state.P1.direction === Movement.Up) { 213 | this.state.P1.direction = Movement.None 214 | } 215 | break 216 | case 'KeyS': 217 | if (this.state.P1.direction === Movement.Down) { 218 | this.state.P1.direction = Movement.None 219 | } 220 | break 221 | } 222 | }) 223 | } 224 | } 225 | 226 | // https://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element 227 | function getCursorPosition (canvas, event) { 228 | const rect = canvas.getBoundingClientRect() 229 | const x = event.clientX - rect.left 230 | const y = event.clientY - rect.top 231 | return { x: x, y: y } 232 | } 233 | 234 | class State { 235 | P1 236 | P2 237 | ball 238 | terminating 239 | 240 | constructor () { 241 | this.ball = new Ball() 242 | this.P1 = new Player(-0.9) 243 | this.P2 = new Player(0.9) 244 | this.terminating = false 245 | } 246 | 247 | gameOver () { 248 | return this.P1.score > 9 || this.P2.score > 9 249 | } 250 | 251 | currentStage () { 252 | if (this.P1.score >= 9) { 253 | return GameStage.P1Win 254 | } else if (this.P2.score >= 9) { 255 | return GameStage.P2Win 256 | } 257 | return GameStage.Playing 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/pong/shaders/shader.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | varying mediump vec2 screenPosition; 4 | 5 | // 6 | // GAME STATE 7 | // 8 | uniform vec2 score; 9 | uniform vec2 ballPosition; 10 | uniform vec2 leftPaddlePosition; 11 | uniform vec2 rightPaddlePosition; 12 | uniform float gameStage; 13 | uniform vec2 oldPositions[30]; 14 | uniform float time; 15 | 16 | // 17 | // CURVE 18 | // 19 | vec2 curveAmount = vec2(0.8, 0.6); 20 | float caseBorder = 0.0125; 21 | 22 | 23 | float ballRadius = 0.02; 24 | vec2 paddleSize = vec2(0.04, 0.24); 25 | 26 | 27 | // 28 | // DRAWING FUNCTIONS 29 | // 30 | bool isInRect(vec2 position, vec2 center, vec2 size) 31 | { 32 | vec2 delta = abs(center - position); // delta for paddle 0 33 | return delta.x < (size.x/2.0) && delta.y < (size.y/2.0); 34 | } 35 | 36 | float pixelSize = 0.03; 37 | 38 | // xxx 39 | // x x 40 | // x x 41 | // x x 42 | // xxx 43 | bool is0(vec2 position, vec2 pos) 44 | { 45 | return 46 | isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 47 | isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 4.0)) || 48 | isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 49 | isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 4.0)); 50 | } 51 | 52 | 53 | // x 54 | // x 55 | // x 56 | // x 57 | // x 58 | bool is1(vec2 position, vec2 pos) 59 | { 60 | return isInRect(position, pos, vec2(pixelSize, pixelSize * 5.0)); 61 | } 62 | 63 | // xxx 64 | // x 65 | // xxx 66 | // x 67 | // xxx 68 | bool is2(vec2 position, vec2 pos) 69 | { 70 | return 71 | isInRect(position, pos, vec2(pixelSize * 3.0, pixelSize)) || 72 | isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 73 | isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 74 | isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y + 1.0 * pixelSize), vec2(pixelSize, pixelSize)) || 75 | isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y - 1.0 * pixelSize), vec2(pixelSize, pixelSize)); 76 | } 77 | 78 | // xxx 79 | // x 80 | // xxx 81 | // x 82 | // xxx 83 | bool is3(vec2 position, vec2 pos) 84 | { 85 | return 86 | isInRect(position, pos, vec2(pixelSize * 3.0, pixelSize)) || 87 | isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 88 | isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 89 | isInRect(position, vec2(pos.x + pixelSize, pos.y), vec2(pixelSize, 3.0 * pixelSize)); 90 | } 91 | 92 | // x x 93 | // x x 94 | // xxx 95 | // x 96 | // x 97 | bool is4(vec2 position, vec2 pos) 98 | { 99 | return 100 | isInRect(position, pos, vec2(pixelSize * 3.0, pixelSize)) || 101 | isInRect(position, vec2(pos.x - pixelSize, pos.y + pixelSize), vec2(pixelSize, 3.0 * pixelSize)) || 102 | isInRect(position, vec2(pos.x + pixelSize, pos.y), vec2(pixelSize, 5.0 * pixelSize)); 103 | } 104 | 105 | // xxx 106 | // x 107 | // xxx 108 | // x 109 | // xxx 110 | bool is5(vec2 position, vec2 pos) 111 | { 112 | return 113 | isInRect(position, pos, vec2(pixelSize * 3.0, pixelSize)) || 114 | isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 115 | isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 116 | isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y + 1.0 * pixelSize), vec2(pixelSize, pixelSize)) || 117 | isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y - 1.0 * pixelSize), vec2(pixelSize, pixelSize)); 118 | } 119 | 120 | // xxx 121 | // x 122 | // xxx 123 | // x x 124 | // xxx 125 | bool is6(vec2 position, vec2 pos) 126 | { 127 | return 128 | isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 129 | isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 4.0)) || 130 | isInRect(position, vec2(pos.x, pos.y), vec2(pixelSize * 3.0, pixelSize)) || 131 | isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 132 | isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y - 1.0 * pixelSize), vec2(pixelSize, pixelSize * 3.0)); 133 | } 134 | 135 | // xxx 136 | // x 137 | // x 138 | // x 139 | // x 140 | bool is7(vec2 position, vec2 pos) 141 | { 142 | return 143 | isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 144 | isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 4.0)); 145 | } 146 | 147 | // xxx 148 | // x x 149 | // xxx 150 | // x x 151 | // xxx 152 | bool is8(vec2 position, vec2 pos) 153 | { 154 | return 155 | isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 156 | isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 3.0)) || 157 | isInRect(position, vec2(pos.x, pos.y), vec2(pixelSize * 3.0, pixelSize)) || 158 | isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 159 | isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 3.0)); 160 | } 161 | 162 | // xxx 163 | // x x 164 | // xxx 165 | // x 166 | // xxx 167 | bool is9(vec2 position, vec2 pos) 168 | { 169 | return 170 | isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 171 | isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y + 1.0 * pixelSize), vec2(pixelSize, pixelSize * 3.0)) || 172 | isInRect(position, vec2(pos.x, pos.y), vec2(pixelSize * 3.0, pixelSize)) || 173 | isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 174 | isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 4.0)); 175 | } 176 | 177 | // xxx 178 | // x x 179 | // xxx 180 | // x 181 | // x 182 | bool isP(vec2 position, vec2 pos) 183 | { 184 | return 185 | isInRect(position, vec2(pos.x - pixelSize, pos.y), vec2(pixelSize, 5.0 * pixelSize)) || 186 | isInRect(position, pos, vec2(pixelSize, pixelSize)) || 187 | isInRect(position, vec2(pos.x + pixelSize, pos.y + pixelSize), vec2(pixelSize, 3.0 * pixelSize)) || 188 | isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize, pixelSize)); 189 | } 190 | 191 | bool isO(vec2 position, vec2 pos) 192 | { 193 | return is0(position, pos); 194 | } 195 | 196 | // xxx 197 | // x x 198 | // x x 199 | // x x 200 | // x x 201 | bool isN(vec2 position, vec2 pos) 202 | { 203 | return 204 | isInRect(position, vec2(pos.x - pixelSize, pos.y), vec2(pixelSize, 5.0 * pixelSize)) || 205 | isInRect(position, vec2(pos.x + pixelSize, pos.y), vec2(pixelSize, 5.0 * pixelSize)) || 206 | isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(3.0 * pixelSize, pixelSize)); 207 | } 208 | 209 | 210 | // xxx 211 | // x 212 | // x|x 213 | // x x 214 | // xxx 215 | bool isG(vec2 position, vec2 pos) 216 | { 217 | return 218 | isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 219 | isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) || 220 | isInRect(position, vec2(pos.x - pixelSize, pos.y), vec2(pixelSize, pixelSize * 3.0)) || 221 | isInRect(position, vec2(pos.x + pixelSize, pos.y - pixelSize), vec2(pixelSize)) || 222 | isInRect(position, vec2(pos.x + 0.75 * pixelSize, pos.y), vec2(1.5 * pixelSize, pixelSize)); 223 | } 224 | 225 | // x 226 | // x 227 | // x 228 | // 229 | // x 230 | bool isExclamationMark(vec2 position, vec2 pos) 231 | { 232 | return 233 | isInRect(position, vec2(pos.x, pos.y + pixelSize), vec2(pixelSize, pixelSize * 3.0)) || 234 | isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize)); 235 | } 236 | 237 | 238 | bool isPONG(vec2 position, vec2 pos) 239 | { 240 | return 241 | isP(position, vec2(pos.x - 6.0 * pixelSize, pos.y)) || 242 | isO(position, vec2(pos.x - 2.0 * pixelSize, pos.y)) || 243 | isN(position, vec2(pos.x + 2.0 * pixelSize, pos.y)) || 244 | isG(position, vec2(pos.x + 6.0 * pixelSize, pos.y)); 245 | } 246 | 247 | 248 | bool isTriangle(vec2 position, vec2 size, vec2 pos) 249 | { 250 | float top = pos.y + size.y; 251 | float bottom = pos.y - size.y; 252 | float start = pos.x; 253 | float end = pos.x + size.x; 254 | 255 | float slope = size.y/size.x; 256 | float relativeX = position.x - start; 257 | 258 | return 259 | position.x > start && position.x < end && // x within triangle 260 | (position.y < top - slope * relativeX && position.y > bottom + slope * relativeX); // y within triangle 261 | 262 | } 263 | 264 | bool isNumber(float number, vec2 position, vec2 numberPosition) 265 | { // GLSL is a beautiful language 266 | if (number == 0.0) { return is0(position, numberPosition); } 267 | if (number == 1.0) { return is1(position, numberPosition); } 268 | if (number == 2.0) { return is2(position, numberPosition); } 269 | if (number == 3.0) { return is3(position, numberPosition); } 270 | if (number == 4.0) { return is4(position, numberPosition); } 271 | if (number == 5.0) { return is5(position, numberPosition); } 272 | if (number == 6.0) { return is6(position, numberPosition); } 273 | if (number == 7.0) { return is7(position, numberPosition); } 274 | if (number == 8.0) { return is8(position, numberPosition); } 275 | if (number == 9.0) { return is9(position, numberPosition); } 276 | return false; 277 | } 278 | 279 | bool isP1score(vec2 position) { 280 | return isNumber(score.x, position, vec2(-4.0 * pixelSize, 18.0 * pixelSize)); 281 | } 282 | 283 | bool isP2score(vec2 position) { 284 | return isNumber(score.y, position, vec2(4.0 * pixelSize, 18.0 * pixelSize)); 285 | } 286 | 287 | bool isLine(vec2 position) { 288 | return 289 | abs(position.x) < pixelSize / 2.0 && 290 | mod(position.y - pixelSize * 1.5, pixelSize * 2.0) <= pixelSize; 291 | } 292 | 293 | bool isWinLine(vec2 position) { 294 | return isLine(position) && 295 | (position.y > pixelSize * 3.0 || position.y < pixelSize * -3.0); 296 | } 297 | 298 | bool isPlayButton(vec2 position) { 299 | return isTriangle(position, vec2(0.16, 0.1), vec2(-0.06, 0)); 300 | } 301 | 302 | // 303 | // GAMESTAGE SPECIFIC RENDERING 304 | // 305 | vec4 welcome(vec2 position) { // GameStage.Welcome 306 | if (isPONG(position, vec2(0, 0.3)) || 307 | isPlayButton(position)) 308 | { 309 | return vec4(1, 1, 1, 1); 310 | } 311 | return vec4(0, 0, 0, 1.0); 312 | } 313 | 314 | vec4 playing(vec2 position) // GameStage.Playing 315 | { 316 | bool isLeftPaddle = isInRect(position, leftPaddlePosition, paddleSize); 317 | bool isRightPaddle = isInRect(position, rightPaddlePosition, paddleSize); 318 | bool isBall = isInRect(position, ballPosition, vec2(ballRadius * 2.0, ballRadius * 2.0)); 319 | 320 | if (isLeftPaddle || 321 | isRightPaddle || 322 | isBall || 323 | isLine(position) || 324 | isP1score(position) || 325 | isP2score(position)) 326 | { 327 | return vec4(1, 1, 1, 1); 328 | } 329 | 330 | // Render tail 331 | for (int i = 29; i >= 0; i--) { 332 | vec2 oldPos = oldPositions[i]; 333 | float scale = float(i) / 30.0; 334 | if (isInRect(position, oldPos, vec2(ballRadius * 2.0 * scale))) { 335 | return vec4(scale, scale, scale, 1.0); 336 | } 337 | } 338 | 339 | return vec4(0, 0, 0, 1.0); 340 | } 341 | 342 | vec4 p1win(vec2 position) // GameStage.P1Win 343 | { 344 | if (isP(position, vec2(-19.0 * pixelSize, 0)) || 345 | is1(position, vec2(-16.0 * pixelSize, 0)) || 346 | isExclamationMark(position, vec2(-14.0 * pixelSize, 0)) || 347 | isP1score(position) || 348 | isP2score(position) || 349 | isWinLine(position) || 350 | isPlayButton(position) 351 | ) 352 | { 353 | return vec4(1, 1, 1, 1); 354 | } 355 | return vec4(0, 0, 0, 1.0); 356 | } 357 | 358 | vec4 p2win(vec2 position) // GameStage.P2Win 359 | { 360 | if (isP(position, vec2(14.0 * pixelSize, 0)) || 361 | is2(position, vec2(18.0 * pixelSize, 0)) || 362 | isExclamationMark(position, vec2(21.0 * pixelSize, 0)) || 363 | isP1score(position) || 364 | isP2score(position) || 365 | isWinLine(position) || 366 | isPlayButton(position) 367 | ) 368 | { 369 | return vec4(1, 1, 1, 1); 370 | } 371 | return vec4(0, 0, 0, 1.0); 372 | } 373 | 374 | vec4 scanline(vec4 color, vec2 position) { 375 | float lineHeight = 0.0104; 376 | float darkness = 0.9; 377 | float speed = 0.0104; 378 | 379 | float y = mod(position.y + (time / 60.0) * speed, lineHeight); 380 | if (y < lineHeight/2.0) { 381 | return color; 382 | } else { 383 | return vec4(color.x, color.y, color.z, darkness); 384 | } 385 | } 386 | 387 | vec4 colorAt(vec2 position) { 388 | // Map position from [0, 1] to [-1, 1] to get world space color 389 | position *= 2.0; 390 | position -= vec2(1.0); 391 | 392 | vec4 color; 393 | 394 | if (abs(position.y) > 0.666) { // out of render area 395 | return vec4(0, 0, 0, 1); 396 | } 397 | 398 | if (gameStage == 0.0) { // GameStage.Welcome 399 | color = welcome(position); 400 | } else if (gameStage == 1.0) { // GameStage.Playing 401 | color = playing(position); 402 | } else if (gameStage == 2.0) { // GameStage.P1Win 403 | color = p1win(position); 404 | } else if (gameStage == 3.0) { // GameStage.P2Win 405 | color = p2win(position); 406 | } 407 | 408 | // Apply scanline effect 409 | color = scanline(color, position); 410 | 411 | return color; 412 | } 413 | 414 | 415 | vec4 curve(vec2 position) { 416 | // Map position from [-1, 1] to [0, 1] for curve effect 417 | position += vec2(1.0); 418 | position /= 2.0; 419 | 420 | // Using this https://github.com/wessles/GLSL-CRT/blob/master/shader.frag algorithm 421 | float dx = abs(0.5 - position.x); 422 | float dy = abs(0.5 - position.y); 423 | dx *= dx; 424 | dy *= dy; 425 | 426 | position.x -= 0.5; 427 | position.x *= 1.0 + (dy * curveAmount.x); 428 | position.x += 0.5; 429 | 430 | position.y -= 0.5; 431 | position.y *= 1.0 + (dx * curveAmount.y); 432 | position.y += 0.5; 433 | 434 | // Draw color from world space 435 | vec4 color = colorAt(position); 436 | color += sin(position.y) * 0.02; 437 | 438 | if(position.y > 1.0 || position.x < 0.0 || position.x > 1.0 || position.y < 0.0) 439 | color = vec4(0, 0, 0, 1); 440 | 441 | return color; 442 | } 443 | 444 | void main() { 445 | gl_FragColor = curve(screenPosition); 446 | } 447 | -------------------------------------------------------------------------------- /docs/js/app.4b46cf16.js: -------------------------------------------------------------------------------- 1 | (function(e){function i(i){for(var t,r,a=i[0],c=i[1],l=i[2],p=0,u=[];p30&&this.oldPositions.shift(),this.x=e,this.y=i}}]),Object(d["a"])(e,[{key:"left",value:function(){return this.x-this.radius}},{key:"right",value:function(){return this.x+this.radius}},{key:"top",value:function(){return this.y+this.radius}},{key:"bottom",value:function(){return this.y-this.radius}}]),e}()),m=new Audio(n("805d")),z=new Audio(n("c21e")),P=new Audio(n("e57d")),w=new Audio(n("dc58")),R={Welcome:0,Playing:1,P1Win:2,P2Win:3},I=function(){function e(i,n,t){Object(h["a"])(this,e),Object(f["a"])(this,"countdown",void 0),Object(f["a"])(this,"stage",void 0),Object(f["a"])(this,"canvas",void 0),Object(f["a"])(this,"center",void 0),Object(f["a"])(this,"renderer",void 0),Object(f["a"])(this,"state",void 0),Object(f["a"])(this,"maxY",void 0),Object(f["a"])(this,"time",void 0),this.countdown=60,this.time=0,this.stage="undefined"!==typeof t?t:R.Playing,this.canvas=n,this.renderer=i,this.maxY=.65,this.state=new k,this.center={x:n.width/2,y:n.height/2}}return Object(d["a"])(e,[{key:"tick",value:function(){this.time++,this.renderer.setTime(this.time),this.stage===R.Playing?(this.state=this.transition(this.state),this.syncState()):this.renderer.setGameStage(this.stage),this.draw()}},{key:"transition",value:function(e){if(this.countdown--,this.stage=this.state.currentStage(),this.updatePlayerState(e.P1),this.updatePlayerState(e.P2),this.countdown>0)return e;if(0===this.countdown&&P.play(),this.updateBallState(e.ball),e.ball.x<=-1){var i=new k;return i.P1=e.P1,i.P2=e.P2,i.P2.score++,m.play(),this.countdown=60,i}if(e.ball.x>=1){var n=new k;return n.P1=e.P1,n.P2=e.P2,n.P1.score++,this.countdown=60,m.play(),n}if(e.terminating)return e;var t=5*Math.PI/12;if(e.ball.left()e.P1.bottom()||e.ball.top()e.P1.bottom()){z.play();var o=e.P1.y-e.ball.y,s=o/(e.P1.height/2),r=s*t;e.ball.speed*=e.ball.acceleration,e.ball.direction.x=e.ball.speed*Math.cos(r),e.ball.direction.y=-e.ball.speed*Math.sin(r)}else e.terminating=!0;if(e.ball.right()>e.P2.left())if(e.ball.bottom()e.P2.bottom()||e.ball.top()e.P2.bottom()){z.play();var a=e.P2.y-e.ball.y,c=a/(e.P2.height/2),l=c*t;e.ball.speed*=e.ball.acceleration,e.ball.direction.x=e.ball.speed*-Math.cos(l),e.ball.direction.y=e.ball.speed*-Math.sin(l)}else e.terminating=!0;return this.state}},{key:"updateBallState",value:function(e){e.setLocation(e.x+e.direction.x,e.y+e.direction.y),Math.abs(e.y)>this.maxY&&(w.play(),e.direction.y*=-1)}},{key:"updatePlayerState",value:function(e){switch(e.direction){case b.Up:e.y=Math.min(e.y+e.speed,this.maxY-e.height/2);break;case b.Down:e.y=Math.max(e.y-e.speed,-this.maxY+e.height/2);break}}},{key:"syncState",value:function(){this.renderer.setOldPositions(this.state.ball.oldPositions),this.renderer.setGameStage(this.stage),this.renderer.setLeftPaddlePosition(this.state.P1.x,this.state.P1.y),this.renderer.setRightPaddlePosition(this.state.P2.x,this.state.P2.y),this.renderer.setBallPosition(this.state.ball.x,this.state.ball.y),this.renderer.setScore(this.state.P1.score,this.state.P2.score)}},{key:"draw",value:function(){this.renderer.draw()}},{key:"addEventListeners",value:function(){var e=this;this.canvas.addEventListener("mousedown",(function(i){var n=50,t=O(e.canvas,i);Math.abs(e.center.x-t.x)9||this.P2.score>9}},{key:"currentStage",value:function(){return this.P1.score>=9?R.P1Win:this.P2.score>=9?R.P2Win:R.Playing}}]),e}(),j=(n("ddb0"),n("3835")),A=n("b85c"),L=n("20e7"),U=(n("ac1f"),n("5319"),function(){function e(i,n,t){Object(h["a"])(this,e),Object(f["a"])(this,"gl",void 0),Object(f["a"])(this,"program",void 0),this.gl=i,this.program=this.initShaderProgram(i,n,t)}return Object(d["a"])(e,[{key:"initShaderProgram",value:function(e,i,n){var t=this.loadShader(e,e.VERTEX_SHADER,i),o=this.loadShader(e,e.FRAGMENT_SHADER,n),s=e.createProgram();return e.attachShader(s,t),e.attachShader(s,o),e.linkProgram(s),e.getProgramParameter(s,e.LINK_STATUS)?s:(alert("Unable to initialize the shader program: ".concat(e.getProgramInfoLog(s))),null)}},{key:"preprocessSource",value:function(e,i){var n=/42\/\/\$(.*)\$\/\//g;return e.replace(n,(function(e,n){return i[n]}))}},{key:"loadShader",value:function(e,i,n){var t=e.createShader(i);return e.shaderSource(t,n),e.compileShader(t),e.getShaderParameter(t,e.COMPILE_STATUS)?t:(console.error("An error occurred compiling the shaders:\n".concat(e.getShaderInfoLog(t))),e.deleteShader(t),null)}},{key:"use",value:function(){this.gl.useProgram(this.program)}},{key:"setUniform1f",value:function(e,i){this.gl.uniform1f(this.getUniformLocation(e),i)}},{key:"setUniformMatrix4fv",value:function(e,i){this.gl.uniformMatrix4fv(this.getUniformLocation(e),!1,i)}},{key:"setUniform2fv",value:function(e,i){this.gl.uniform2fv(this.getUniformLocation(e),i)}},{key:"setUniform3fv",value:function(e,i){this.gl.uniform3fv(this.getUniformLocation(e),i)}},{key:"getUniformLocation",value:function(e){return this.gl.getUniformLocation(this.program,e)}},{key:"getAttribLocation",value:function(e){return this.gl.getAttribLocation(this.program,e)}},{key:"delete",value:function(){this.gl.deleteProgram(this.program)}}]),e}()),E="precision highp float;\n\nvarying mediump vec2 screenPosition;\n\n//\n// GAME STATE\n//\nuniform vec2 score;\nuniform vec2 ballPosition;\nuniform vec2 leftPaddlePosition;\nuniform vec2 rightPaddlePosition;\nuniform float gameStage;\nuniform vec2 oldPositions[30];\nuniform float time;\n\n//\n// CURVE\n//\nvec2 curveAmount = vec2(0.8, 0.6);\nfloat caseBorder = 0.0125;\n\n\nfloat ballRadius = 0.02;\nvec2 paddleSize = vec2(0.04, 0.24);\n\n\n//\n// DRAWING FUNCTIONS\n//\nbool isInRect(vec2 position, vec2 center, vec2 size)\n{\n vec2 delta = abs(center - position); // delta for paddle 0\n return delta.x < (size.x/2.0) && delta.y < (size.y/2.0);\n}\n\nfloat pixelSize = 0.03;\n\n// xxx\n// x x\n// x x\n// x x\n// xxx\nbool is0(vec2 position, vec2 pos)\n{\n return\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 4.0)) ||\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 4.0));\n}\n\n\n// x\n// x\n// x\n// x\n// x\nbool is1(vec2 position, vec2 pos)\n{\n return isInRect(position, pos, vec2(pixelSize, pixelSize * 5.0));\n}\n\n// xxx\n// x\n// xxx\n// x\n// xxx\nbool is2(vec2 position, vec2 pos)\n{\n return\n isInRect(position, pos, vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y + 1.0 * pixelSize), vec2(pixelSize, pixelSize)) ||\n isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y - 1.0 * pixelSize), vec2(pixelSize, pixelSize));\n}\n\n// xxx\n// x\n// xxx\n// x\n// xxx\nbool is3(vec2 position, vec2 pos)\n{\n return\n isInRect(position, pos, vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x + pixelSize, pos.y), vec2(pixelSize, 3.0 * pixelSize));\n}\n\n// x x\n// x x\n// xxx\n// x\n// x\nbool is4(vec2 position, vec2 pos)\n{\n return\n isInRect(position, pos, vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x - pixelSize, pos.y + pixelSize), vec2(pixelSize, 3.0 * pixelSize)) ||\n isInRect(position, vec2(pos.x + pixelSize, pos.y), vec2(pixelSize, 5.0 * pixelSize));\n}\n\n// xxx\n// x\n// xxx\n// x\n// xxx\nbool is5(vec2 position, vec2 pos)\n{\n return\n isInRect(position, pos, vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y + 1.0 * pixelSize), vec2(pixelSize, pixelSize)) ||\n isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y - 1.0 * pixelSize), vec2(pixelSize, pixelSize));\n}\n\n// xxx\n// x\n// xxx\n// x x\n// xxx\nbool is6(vec2 position, vec2 pos)\n{\n return\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 4.0)) ||\n isInRect(position, vec2(pos.x, pos.y), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y - 1.0 * pixelSize), vec2(pixelSize, pixelSize * 3.0));\n}\n\n// xxx\n// x\n// x\n// x\n// x\nbool is7(vec2 position, vec2 pos)\n{\n return\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 4.0));\n}\n\n// xxx\n// x x\n// xxx\n// x x\n// xxx\nbool is8(vec2 position, vec2 pos)\n{\n return\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 3.0)) ||\n isInRect(position, vec2(pos.x, pos.y), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 3.0));\n}\n\n// xxx\n// x x\n// xxx\n// x\n// xxx\nbool is9(vec2 position, vec2 pos)\n{\n return\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y + 1.0 * pixelSize), vec2(pixelSize, pixelSize * 3.0)) ||\n isInRect(position, vec2(pos.x, pos.y), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 4.0));\n}\n\n// xxx\n// x x\n// xxx\n// x\n// x\nbool isP(vec2 position, vec2 pos)\n{\n return\n isInRect(position, vec2(pos.x - pixelSize, pos.y), vec2(pixelSize, 5.0 * pixelSize)) ||\n isInRect(position, pos, vec2(pixelSize, pixelSize)) ||\n isInRect(position, vec2(pos.x + pixelSize, pos.y + pixelSize), vec2(pixelSize, 3.0 * pixelSize)) ||\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize, pixelSize));\n}\n\nbool isO(vec2 position, vec2 pos)\n{\n return is0(position, pos);\n}\n\n// xxx\n// x x\n// x x\n// x x\n// x x\nbool isN(vec2 position, vec2 pos)\n{\n return\n isInRect(position, vec2(pos.x - pixelSize, pos.y), vec2(pixelSize, 5.0 * pixelSize)) ||\n isInRect(position, vec2(pos.x + pixelSize, pos.y), vec2(pixelSize, 5.0 * pixelSize)) ||\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(3.0 * pixelSize, pixelSize));\n}\n\n\n// xxx\n// x\n// x|x\n// x x\n// xxx\nbool isG(vec2 position, vec2 pos)\n{\n return\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\n isInRect(position, vec2(pos.x - pixelSize, pos.y), vec2(pixelSize, pixelSize * 3.0)) ||\n isInRect(position, vec2(pos.x + pixelSize, pos.y - pixelSize), vec2(pixelSize)) ||\n isInRect(position, vec2(pos.x + 0.75 * pixelSize, pos.y), vec2(1.5 * pixelSize, pixelSize));\n}\n\n// x\n// x\n// x\n//\n// x\nbool isExclamationMark(vec2 position, vec2 pos)\n{\n return\n isInRect(position, vec2(pos.x, pos.y + pixelSize), vec2(pixelSize, pixelSize * 3.0)) ||\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize));\n}\n\n\nbool isPONG(vec2 position, vec2 pos)\n{\n return\n isP(position, vec2(pos.x - 6.0 * pixelSize, pos.y)) ||\n isO(position, vec2(pos.x - 2.0 * pixelSize, pos.y)) ||\n isN(position, vec2(pos.x + 2.0 * pixelSize, pos.y)) ||\n isG(position, vec2(pos.x + 6.0 * pixelSize, pos.y));\n}\n\n\nbool isTriangle(vec2 position, vec2 size, vec2 pos)\n{\n float top = pos.y + size.y;\n float bottom = pos.y - size.y;\n float start = pos.x;\n float end = pos.x + size.x;\n\n float slope = size.y/size.x;\n float relativeX = position.x - start;\n\n return\n position.x > start && position.x < end && // x within triangle\n (position.y < top - slope * relativeX && position.y > bottom + slope * relativeX); // y within triangle\n\n}\n\nbool isNumber(float number, vec2 position, vec2 numberPosition)\n{ // GLSL is a beautiful language\n if (number == 0.0) { return is0(position, numberPosition); }\n if (number == 1.0) { return is1(position, numberPosition); }\n if (number == 2.0) { return is2(position, numberPosition); }\n if (number == 3.0) { return is3(position, numberPosition); }\n if (number == 4.0) { return is4(position, numberPosition); }\n if (number == 5.0) { return is5(position, numberPosition); }\n if (number == 6.0) { return is6(position, numberPosition); }\n if (number == 7.0) { return is7(position, numberPosition); }\n if (number == 8.0) { return is8(position, numberPosition); }\n if (number == 9.0) { return is9(position, numberPosition); }\n return false;\n}\n\nbool isP1score(vec2 position) {\n return isNumber(score.x, position, vec2(-4.0 * pixelSize, 18.0 * pixelSize));\n}\n\nbool isP2score(vec2 position) {\n return isNumber(score.y, position, vec2(4.0 * pixelSize, 18.0 * pixelSize));\n}\n\nbool isLine(vec2 position) {\n return\n abs(position.x) < pixelSize / 2.0 &&\n mod(position.y - pixelSize * 1.5, pixelSize * 2.0) <= pixelSize;\n}\n\nbool isWinLine(vec2 position) {\n return isLine(position) &&\n (position.y > pixelSize * 3.0 || position.y < pixelSize * -3.0);\n}\n\nbool isPlayButton(vec2 position) {\n return isTriangle(position, vec2(0.16, 0.1), vec2(-0.06, 0));\n}\n\n//\n// GAMESTAGE SPECIFIC RENDERING\n//\nvec4 welcome(vec2 position) { // GameStage.Welcome\n if (isPONG(position, vec2(0, 0.3)) ||\n isPlayButton(position))\n {\n return vec4(1, 1, 1, 1);\n }\n return vec4(0, 0, 0, 1.0);\n}\n\nvec4 playing(vec2 position) // GameStage.Playing\n{\n bool isLeftPaddle = isInRect(position, leftPaddlePosition, paddleSize);\n bool isRightPaddle = isInRect(position, rightPaddlePosition, paddleSize);\n bool isBall = isInRect(position, ballPosition, vec2(ballRadius * 2.0, ballRadius * 2.0));\n\n if (isLeftPaddle ||\n isRightPaddle ||\n isBall ||\n isLine(position) ||\n isP1score(position) ||\n isP2score(position))\n {\n return vec4(1, 1, 1, 1);\n }\n\n // Render tail\n for (int i = 29; i >= 0; i--) {\n vec2 oldPos = oldPositions[i];\n float scale = float(i) / 30.0;\n if (isInRect(position, oldPos, vec2(ballRadius * 2.0 * scale))) {\n return vec4(scale, scale, scale, 1.0);\n }\n }\n\n return vec4(0, 0, 0, 1.0);\n}\n\nvec4 p1win(vec2 position) // GameStage.P1Win\n{\n if (isP(position, vec2(-19.0 * pixelSize, 0)) ||\n is1(position, vec2(-16.0 * pixelSize, 0)) ||\n isExclamationMark(position, vec2(-14.0 * pixelSize, 0)) ||\n isP1score(position) ||\n isP2score(position) ||\n isWinLine(position) ||\n isPlayButton(position)\n )\n {\n return vec4(1, 1, 1, 1);\n }\n return vec4(0, 0, 0, 1.0);\n}\n\nvec4 p2win(vec2 position) // GameStage.P2Win\n{\n if (isP(position, vec2(14.0 * pixelSize, 0)) ||\n is2(position, vec2(18.0 * pixelSize, 0)) ||\n isExclamationMark(position, vec2(21.0 * pixelSize, 0)) ||\n isP1score(position) ||\n isP2score(position) ||\n isWinLine(position) ||\n isPlayButton(position)\n )\n {\n return vec4(1, 1, 1, 1);\n }\n return vec4(0, 0, 0, 1.0);\n}\n\nvec4 scanline(vec4 color, vec2 position) {\n float lineHeight = 0.0104;\n float darkness = 0.9;\n float speed = 0.0104;\n\n float y = mod(position.y + (time / 60.0) * speed, lineHeight);\n if (y < lineHeight/2.0) {\n return color;\n } else {\n return vec4(color.x, color.y, color.z, darkness);\n }\n}\n\nvec4 colorAt(vec2 position) {\n // Map position from [0, 1] to [-1, 1] to get world space color\n position *= 2.0;\n position -= vec2(1.0);\n\n vec4 color;\n\n if (abs(position.y) > 0.666) { // out of render area\n return vec4(0, 0, 0, 1);\n }\n\n if (gameStage == 0.0) { // GameStage.Welcome\n color = welcome(position);\n } else if (gameStage == 1.0) { // GameStage.Playing\n color = playing(position);\n } else if (gameStage == 2.0) { // GameStage.P1Win\n color = p1win(position);\n } else if (gameStage == 3.0) { // GameStage.P2Win\n color = p2win(position);\n }\n\n // Apply scanline effect\n color = scanline(color, position);\n\n return color;\n}\n\n\nvec4 curve(vec2 position) {\n // Map position from [-1, 1] to [0, 1] for curve effect\n position += vec2(1.0);\n position /= 2.0;\n\n // Using this https://github.com/wessles/GLSL-CRT/blob/master/shader.frag algorithm\n float dx = abs(0.5 - position.x);\n float dy = abs(0.5 - position.y);\n dx *= dx;\n dy *= dy;\n\n position.x -= 0.5;\n position.x *= 1.0 + (dy * curveAmount.x);\n position.x += 0.5;\n\n position.y -= 0.5;\n position.y *= 1.0 + (dx * curveAmount.y);\n position.y += 0.5;\n\n // Draw color from world space\n vec4 color = colorAt(position);\n color += sin(position.y) * 0.02;\n\n if(position.y > 1.0 || position.x < 0.0 || position.x > 1.0 || position.y < 0.0)\n color = vec4(0, 0, 0, 1);\n\n return color;\n}\n\nvoid main() {\n gl_FragColor = curve(screenPosition);\n}\n",M="precision highp float;\n\nattribute vec4 aVertexPosition;\n\nuniform mat4 uModelViewMatrix;\nuniform mat4 uProjectionMatrix;\n\nvarying mediump vec2 screenPosition;\n\nvoid main() {\n screenPosition = aVertexPosition.xy;\n gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;\n}\n",T=function(){function e(i){Object(h["a"])(this,e),Object(f["a"])(this,"gl",void 0),Object(f["a"])(this,"shader",void 0),Object(f["a"])(this,"buffers",void 0),this.gl=i,this.shader=new U(i,M,E),this.buffers=this.initBuffers(),this.shader.use(),this.setDefaultPositions()}return Object(d["a"])(e,[{key:"recompileShader",value:function(e){this.shader.delete(),this.shader=new U(this.gl,M,E,e)}},{key:"initBuffers",value:function(){var e=this.gl.createBuffer();this.gl.bindBuffer(this.gl.ARRAY_BUFFER,e);var i=[-1,1,1,1,-1,-1,1,-1];return this.gl.bufferData(this.gl.ARRAY_BUFFER,new Float32Array(i),this.gl.STATIC_DRAW),{position:e}}},{key:"draw",value:function(){this.gl.clearColor(0,0,0,1),this.gl.clear(this.gl.COLOR_BUFFER_BIT),this.gl.clearDepth(1),this.gl.enable(this.gl.DEPTH_TEST),this.gl.depthFunc(this.gl.LEQUAL),this.gl.clear(this.gl.COLOR_BUFFER_BIT|this.gl.DEPTH_BUFFER_BIT);var e=29.8*Math.PI/180,i=this.gl.canvas.clientWidth/this.gl.canvas.clientHeight,n=.1,t=100,o=L["a"].create();L["a"].perspective(o,e,i,n,t);var s=L["a"].create();L["a"].translate(s,s,[0,0,-2.5]);var r=this.shader.getAttribLocation("aVertexPosition");this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.buffers.position),this.gl.vertexAttribPointer(r,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(r),this.shader.use(),this.shader.setUniformMatrix4fv("uProjectionMatrix",o),this.shader.setUniformMatrix4fv("uModelViewMatrix",s);var a=0,c=4;this.gl.drawArrays(this.gl.TRIANGLE_STRIP,a,c)}},{key:"setOldPositions",value:function(e){var i,n=Object(A["a"])(e.entries());try{for(n.s();!(i=n.n()).done;){var t=Object(j["a"])(i.value,2),o=t[0],s=t[1];this.shader.setUniform2fv("oldPositions[".concat(o,"]"),s)}}catch(r){n.e(r)}finally{n.f()}}},{key:"setTime",value:function(e){this.shader.setUniform1f("time",e)}},{key:"setGameStage",value:function(e){this.shader.setUniform1f("gameStage",e)}},{key:"setDefaultPositions",value:function(){this.setLeftPaddlePosition(-.9,0),this.setRightPaddlePosition(.9,0),this.setBallPosition(0,0)}},{key:"setLeftPaddlePosition",value:function(e,i){this.shader.setUniform2fv("leftPaddlePosition",new Float32Array([e,i]))}},{key:"setRightPaddlePosition",value:function(e,i){this.shader.setUniform2fv("rightPaddlePosition",new Float32Array([e,i]))}},{key:"setBallPosition",value:function(e,i){this.shader.setUniform2fv("ballPosition",new Float32Array([e,i]))}},{key:"setScore",value:function(e,i){this.shader.setUniform2fv("score",new Float32Array([e,i]))}}]),e}(),_={name:"Home",data:function(){return{pong:null,pongRenderer:null}},mounted:function(){var e=this,i=document.querySelector("#glCanvas"),n=i.getContext("webgl");null===n&&alert("Unable to initialize WebGL. Your browser or machine may not support it."),this.pongRenderer=new T(n),this.pong=new I(this.pongRenderer,i,R.Welcome),this.pong.addEventListeners();var t=function i(n){e.pong.tick(),requestAnimationFrame(i)};requestAnimationFrame(t)}},F=_,B=(n("21bb"),Object(r["a"])(F,u,v,!1,null,null,null)),G=B.exports;t["a"].use(x["a"]);var N=[{path:"/",name:"Home",component:G},{path:"/about",name:"About",component:function(){return n.e("about").then(n.bind(null,"f820"))}}],D=new x["a"]({mode:"history",base:"/infogr-pong/",routes:N}),W=D,C=n("2f62");t["a"].use(C["a"]);var H=new C["a"].Store({state:{},mutations:{},actions:{},modules:{}});t["a"].config.productionTip=!1,new t["a"]({router:W,store:H,render:function(e){return e(l)}}).$mount("#app")},"5c0b":function(e,i,n){"use strict";var t=n("9c0c"),o=n.n(t);o.a},"805d":function(e,i,n){e.exports=n.p+"media/score.63ce9ecd.wav"},"9c0c":function(e,i,n){},c21e:function(e,i,n){e.exports=n.p+"media/hit.f8648124.wav"},dc58:function(e,i,n){e.exports=n.p+"media/side.f37fcc5f.wav"},e57d:function(e,i,n){e.exports=n.p+"media/start.1f4848ab.wav"}}); 2 | //# sourceMappingURL=app.4b46cf16.js.map -------------------------------------------------------------------------------- /docs/js/app.4b46cf16.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///webpack/bootstrap","webpack:///./src/views/Home.vue?da68","webpack:///./src/App.vue?bf48","webpack:///./src/App.vue","webpack:///./src/registerServiceWorker.js","webpack:///./src/views/Home.vue?669d","webpack:///./src/pong/movement.js","webpack:///./src/pong/player.js","webpack:///./src/pong/ball.js","webpack:///./src/pong/pong.js","webpack:///./src/pong/shader.js","webpack:///./src/pong/shaders/shader.frag","webpack:///./src/pong/shaders/shader.vert","webpack:///./src/pong/pong-renderer.js","webpack:///src/views/Home.vue","webpack:///./src/views/Home.vue?493c","webpack:///./src/views/Home.vue","webpack:///./src/router/index.js","webpack:///./src/store/index.js","webpack:///./src/main.js","webpack:///./src/App.vue?c650","webpack:///./src/assets/score.wav","webpack:///./src/assets/hit.wav","webpack:///./src/assets/side.wav","webpack:///./src/assets/start.wav"],"names":["webpackJsonpCallback","data","moduleId","chunkId","chunkIds","moreModules","executeModules","i","resolves","length","Object","prototype","hasOwnProperty","call","installedChunks","push","modules","parentJsonpFunction","shift","deferredModules","apply","checkDeferredModules","result","deferredModule","fulfilled","j","depId","splice","__webpack_require__","s","installedModules","jsonpScriptSrc","p","exports","module","l","e","promises","installedChunkData","promise","Promise","resolve","reject","onScriptComplete","script","document","createElement","charset","timeout","nc","setAttribute","src","error","Error","event","onerror","onload","clearTimeout","chunk","errorType","type","realSrc","target","message","name","request","undefined","setTimeout","head","appendChild","all","m","c","d","getter","o","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","oe","err","console","jsonpArray","window","oldJsonpFunction","slice","_vm","this","_h","$createElement","_c","_self","attrs","staticRenderFns","component","register","process","ready","log","registered","cached","updatefound","updated","offline","_m","staticClass","Movement","None","Up","Down","Player","x","width","height","y","speed","direction","score","Ball","radius","startDirections","Math","floor","random","acceleration","oldPositions","Float32Array","ScoreAudio","Audio","require","PlopAudio","StartAudio","SideAudio","GameStage","Welcome","Playing","P1Win","P2Win","Pong","renderer","canvas","stage","countdown","time","maxY","state","State","center","setTime","transition","syncState","setGameStage","draw","currentStage","updatePlayerState","P1","P2","play","updateBallState","ball","newState","terminating","maxBounceAngle","PI","left","right","bottom","top","distanceToNormal","normalizedDistanceToNormal","bounceAngle","cos","sin","setLocation","abs","P","min","max","setOldPositions","setLeftPaddlePosition","setRightPaddlePosition","setBallPosition","setScore","addEventListener","hitboxSize","click","getCursorPosition","k","code","rect","getBoundingClientRect","clientX","clientY","Shader","gl","vsSource","fsSource","program","initShaderProgram","vertexShader","loadShader","VERTEX_SHADER","fragmentShader","FRAGMENT_SHADER","shaderProgram","createProgram","attachShader","linkProgram","getProgramParameter","LINK_STATUS","alert","getProgramInfoLog","source","sourceVars","sourceVarPattern","replace","_","varName","shader","createShader","shaderSource","compileShader","getShaderParameter","COMPILE_STATUS","getShaderInfoLog","deleteShader","useProgram","uniform1f","getUniformLocation","uniformMatrix4fv","uniform2fv","uniform3fv","getAttribLocation","deleteProgram","PongRenderer","buffers","initBuffers","use","setDefaultPositions","shaderSourceVars","delete","positionBuffer","createBuffer","bindBuffer","ARRAY_BUFFER","positions","bufferData","STATIC_DRAW","position","clearColor","clear","COLOR_BUFFER_BIT","clearDepth","enable","DEPTH_TEST","depthFunc","LEQUAL","DEPTH_BUFFER_BIT","fieldOfView","aspect","clientWidth","clientHeight","zNear","zFar","projectionMatrix","mat4","perspective","modelViewMatrix","translate","vertexPositionLocation","vertexAttribPointer","FLOAT","enableVertexAttribArray","setUniformMatrix4fv","offset","vertexCount","drawArrays","TRIANGLE_STRIP","entries","pos","setUniform2fv","setUniform1f","gameStage","pong","pongRenderer","mounted","addEventListeners","requestAnimationFrame","render","Vue","VueRouter","routes","path","Home","router","base","Vuex","Store","mutations","actions","config","productionTip","store","h","App","$mount"],"mappings":"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","file":"js/app.4b46cf16.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tfunction webpackJsonpCallback(data) {\n \t\tvar chunkIds = data[0];\n \t\tvar moreModules = data[1];\n \t\tvar executeModules = data[2];\n\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(data);\n\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n\n \t\t// add entry modules from loaded chunk to deferred list\n \t\tdeferredModules.push.apply(deferredModules, executeModules || []);\n\n \t\t// run deferred modules when all chunks ready\n \t\treturn checkDeferredModules();\n \t};\n \tfunction checkDeferredModules() {\n \t\tvar result;\n \t\tfor(var i = 0; i < deferredModules.length; i++) {\n \t\t\tvar deferredModule = deferredModules[i];\n \t\t\tvar fulfilled = true;\n \t\t\tfor(var j = 1; j < deferredModule.length; j++) {\n \t\t\t\tvar depId = deferredModule[j];\n \t\t\t\tif(installedChunks[depId] !== 0) fulfilled = false;\n \t\t\t}\n \t\t\tif(fulfilled) {\n \t\t\t\tdeferredModules.splice(i--, 1);\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = deferredModule[0]);\n \t\t\t}\n \t\t}\n\n \t\treturn result;\n \t}\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// undefined = chunk not loaded, null = chunk preloaded/prefetched\n \t// Promise = chunk loading, 0 = chunk loaded\n \tvar installedChunks = {\n \t\t\"app\": 0\n \t};\n\n \tvar deferredModules = [];\n\n \t// script path function\n \tfunction jsonpScriptSrc(chunkId) {\n \t\treturn __webpack_require__.p + \"js/\" + ({\"about\":\"about\"}[chunkId]||chunkId) + \".\" + {\"about\":\"c058c680\"}[chunkId] + \".js\"\n \t}\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n \t// This file contains only the entry chunk.\n \t// The chunk loading function for additional chunks\n \t__webpack_require__.e = function requireEnsure(chunkId) {\n \t\tvar promises = [];\n\n\n \t\t// JSONP chunk loading for javascript\n\n \t\tvar installedChunkData = installedChunks[chunkId];\n \t\tif(installedChunkData !== 0) { // 0 means \"already installed\".\n\n \t\t\t// a Promise means \"currently loading\".\n \t\t\tif(installedChunkData) {\n \t\t\t\tpromises.push(installedChunkData[2]);\n \t\t\t} else {\n \t\t\t\t// setup Promise in chunk cache\n \t\t\t\tvar promise = new Promise(function(resolve, reject) {\n \t\t\t\t\tinstalledChunkData = installedChunks[chunkId] = [resolve, reject];\n \t\t\t\t});\n \t\t\t\tpromises.push(installedChunkData[2] = promise);\n\n \t\t\t\t// start chunk loading\n \t\t\t\tvar script = document.createElement('script');\n \t\t\t\tvar onScriptComplete;\n\n \t\t\t\tscript.charset = 'utf-8';\n \t\t\t\tscript.timeout = 120;\n \t\t\t\tif (__webpack_require__.nc) {\n \t\t\t\t\tscript.setAttribute(\"nonce\", __webpack_require__.nc);\n \t\t\t\t}\n \t\t\t\tscript.src = jsonpScriptSrc(chunkId);\n\n \t\t\t\t// create error before stack unwound to get useful stacktrace later\n \t\t\t\tvar error = new Error();\n \t\t\t\tonScriptComplete = function (event) {\n \t\t\t\t\t// avoid mem leaks in IE.\n \t\t\t\t\tscript.onerror = script.onload = null;\n \t\t\t\t\tclearTimeout(timeout);\n \t\t\t\t\tvar chunk = installedChunks[chunkId];\n \t\t\t\t\tif(chunk !== 0) {\n \t\t\t\t\t\tif(chunk) {\n \t\t\t\t\t\t\tvar errorType = event && (event.type === 'load' ? 'missing' : event.type);\n \t\t\t\t\t\t\tvar realSrc = event && event.target && event.target.src;\n \t\t\t\t\t\t\terror.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';\n \t\t\t\t\t\t\terror.name = 'ChunkLoadError';\n \t\t\t\t\t\t\terror.type = errorType;\n \t\t\t\t\t\t\terror.request = realSrc;\n \t\t\t\t\t\t\tchunk[1](error);\n \t\t\t\t\t\t}\n \t\t\t\t\t\tinstalledChunks[chunkId] = undefined;\n \t\t\t\t\t}\n \t\t\t\t};\n \t\t\t\tvar timeout = setTimeout(function(){\n \t\t\t\t\tonScriptComplete({ type: 'timeout', target: script });\n \t\t\t\t}, 120000);\n \t\t\t\tscript.onerror = script.onload = onScriptComplete;\n \t\t\t\tdocument.head.appendChild(script);\n \t\t\t}\n \t\t}\n \t\treturn Promise.all(promises);\n \t};\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/infogr-pong/\";\n\n \t// on error function for async loading\n \t__webpack_require__.oe = function(err) { console.error(err); throw err; };\n\n \tvar jsonpArray = window[\"webpackJsonp\"] = window[\"webpackJsonp\"] || [];\n \tvar oldJsonpFunction = jsonpArray.push.bind(jsonpArray);\n \tjsonpArray.push = webpackJsonpCallback;\n \tjsonpArray = jsonpArray.slice();\n \tfor(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);\n \tvar parentJsonpFunction = oldJsonpFunction;\n\n\n \t// add entry module to deferred list\n \tdeferredModules.push([0,\"chunk-vendors\"]);\n \t// run deferred modules when ready\n \treturn checkDeferredModules();\n","import mod from \"-!../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--8-oneOf-1-0!../../node_modules/css-loader/dist/cjs.js??ref--8-oneOf-1-1!../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../node_modules/postcss-loader/src/index.js??ref--8-oneOf-1-2!../../node_modules/sass-loader/dist/cjs.js??ref--8-oneOf-1-3!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Home.vue?vue&type=style&index=0&lang=scss&\"; export default mod; export * from \"-!../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--8-oneOf-1-0!../../node_modules/css-loader/dist/cjs.js??ref--8-oneOf-1-1!../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../node_modules/postcss-loader/src/index.js??ref--8-oneOf-1-2!../../node_modules/sass-loader/dist/cjs.js??ref--8-oneOf-1-3!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Home.vue?vue&type=style&index=0&lang=scss&\"","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{attrs:{\"id\":\"app\"}},[_c('router-view')],1)}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import { render, staticRenderFns } from \"./App.vue?vue&type=template&id=50d778a2&\"\nvar script = {}\nimport style0 from \"./App.vue?vue&type=style&index=0&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","/* eslint-disable no-console */\n\nimport { register } from 'register-service-worker'\n\nif (process.env.NODE_ENV === 'production') {\n register(`${process.env.BASE_URL}service-worker.js`, {\n ready () {\n console.log(\n 'App is being served from cache by a service worker.\\n' +\n 'For more details, visit https://goo.gl/AFskqB'\n )\n },\n registered () {\n console.log('Service worker has been registered.')\n },\n cached () {\n console.log('Content has been cached for offline use.')\n },\n updatefound () {\n console.log('New content is downloading.')\n },\n updated () {\n console.log('New content is available; please refresh.')\n },\n offline () {\n console.log('No internet connection found. App is running in offline mode.')\n },\n error (error) {\n console.error('Error during service worker registration:', error)\n }\n })\n}\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _vm._m(0)}\nvar staticRenderFns = [function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"home\"},[_c('div',{staticClass:\"canvas-wrapper\"},[_c('canvas',{attrs:{\"id\":\"glCanvas\",\"width\":\"1200\",\"height\":\"800\"}})])])}]\n\nexport { render, staticRenderFns }","const Movement = {\n None: 0,\n Up: 1,\n Down: 2\n}\n\nexport default Movement\n","import Movement from './movement'\n\nexport default class Player {\n width\n height\n x\n y\n direction\n score\n speed\n\n constructor (x) {\n this.width = 0.04\n this.height = 0.3\n this.x = x\n this.y = 0\n this.speed = 0.02\n this.direction = Movement.None\n this.score = 0\n }\n\n left () {\n return this.x - this.width / 2\n }\n\n right () {\n return this.x + this.width / 2\n }\n\n top () {\n return this.y + this.height / 2\n }\n\n bottom () {\n return this.y - this.height / 2\n }\n}\n","export default class Ball {\n radius\n x\n y\n direction\n oldPositions = []\n speed\n acceleration\n\n startDirections = [\n { x: 0.01, y: 0.01 },\n { x: 0.01, y: -0.01 },\n { x: -0.01, y: 0.01 },\n { x: -0.01, y: -0.01 }\n ]\n\n setLocation (x, y) {\n this.oldPositions.push(new Float32Array([x, y]))\n if (this.oldPositions.length > 30) {\n this.oldPositions.shift()\n }\n this.x = x\n this.y = y\n }\n\n constructor () {\n this.radius = 0.01\n this.x = 0\n this.y = 0\n this.direction = this.startDirections[Math.floor(Math.random() * this.startDirections.length)]\n this.speed = 0.01\n this.acceleration = 1.1\n\n for (var i = 0; i < 30; i++) {\n this.oldPositions[i] = new Float32Array([0, 0])\n }\n }\n\n left () {\n return this.x - this.radius\n }\n\n right () {\n return this.x + this.radius\n }\n\n top () {\n return this.y + this.radius\n }\n\n bottom () {\n return this.y - this.radius\n }\n}\n","import Player from './player'\nimport Movement from './movement'\nimport Ball from './ball'\n\nconst ScoreAudio = new Audio(require('../assets/score.wav'))\nconst PlopAudio = new Audio(require('../assets/hit.wav'))\nconst StartAudio = new Audio(require('../assets/start.wav'))\nconst SideAudio = new Audio(require('../assets/side.wav'))\n\nexport const GameStage = {\n Welcome: 0.0,\n Playing: 1.0,\n P1Win: 2.0,\n P2Win: 3.0\n}\n\nexport class Pong {\n countdown\n stage\n canvas\n center\n renderer\n state\n maxY\n time\n\n constructor (renderer, canvas, stage) {\n this.countdown = 60\n this.time = 0\n if (typeof stage !== 'undefined') {\n this.stage = stage\n } else {\n this.stage = GameStage.Playing\n }\n\n this.canvas = canvas\n this.renderer = renderer\n this.maxY = 0.65\n this.state = new State()\n\n this.center = {\n x: canvas.width / 2,\n y: canvas.height / 2\n }\n }\n\n tick () {\n this.time++\n this.renderer.setTime(this.time)\n if (this.stage === GameStage.Playing) {\n this.state = this.transition(this.state)\n this.syncState()\n } else {\n this.renderer.setGameStage(this.stage)\n }\n this.draw()\n }\n\n transition (state) {\n this.countdown--\n this.stage = this.state.currentStage()\n this.updatePlayerState(state.P1)\n this.updatePlayerState(state.P2)\n\n if (this.countdown > 0) {\n return state\n }\n\n if (this.countdown === 0) {\n StartAudio.play()\n }\n\n this.updateBallState(state.ball)\n\n if (state.ball.x <= -1) {\n const newState = new State()\n newState.P1 = state.P1\n newState.P2 = state.P2\n newState.P2.score++\n ScoreAudio.play()\n this.countdown = 60\n return newState\n } else if (state.ball.x >= 1) {\n const newState = new State()\n newState.P1 = state.P1\n newState.P2 = state.P2\n newState.P1.score++\n this.countdown = 60\n ScoreAudio.play()\n return newState\n }\n\n if (state.terminating) {\n return state\n }\n\n const maxBounceAngle = 5 * Math.PI / 12\n\n // Paddle collision\n if (state.ball.left() < state.P1.right()) {\n if ((state.ball.bottom() < state.P1.top() && state.ball.bottom > state.P1.bottom()) ||\n (state.ball.top() < state.P1.top() && state.ball.top() > state.P1.bottom())) {\n PlopAudio.play()\n const distanceToNormal = state.P1.y - state.ball.y\n const normalizedDistanceToNormal = distanceToNormal / (state.P1.height / 2)\n const bounceAngle = normalizedDistanceToNormal * maxBounceAngle\n\n state.ball.speed *= state.ball.acceleration\n state.ball.direction.x = state.ball.speed * Math.cos(bounceAngle)\n state.ball.direction.y = -state.ball.speed * Math.sin(bounceAngle)\n } else {\n state.terminating = true\n }\n }\n\n if (state.ball.right() > state.P2.left()) {\n if ((state.ball.bottom() < state.P2.top() && state.ball.bottom > state.P2.bottom()) ||\n (state.ball.top() < state.P2.top() && state.ball.top() > state.P2.bottom())) {\n PlopAudio.play()\n const distanceToNormal = state.P2.y - state.ball.y\n const normalizedDistanceToNormal = distanceToNormal / (state.P2.height / 2)\n const bounceAngle = normalizedDistanceToNormal * maxBounceAngle\n\n state.ball.speed *= state.ball.acceleration\n\n state.ball.direction.x = state.ball.speed * -Math.cos(bounceAngle)\n state.ball.direction.y = state.ball.speed * -Math.sin(bounceAngle)\n } else {\n state.terminating = true\n }\n }\n\n // if ball hits paddle, compute new ball direction.\n return this.state\n }\n\n updateBallState (ball) {\n ball.setLocation(ball.x + ball.direction.x, ball.y + ball.direction.y)\n if (Math.abs(ball.y) > this.maxY) {\n SideAudio.play()\n ball.direction.y *= -1\n }\n }\n\n updatePlayerState (P) {\n switch (P.direction) {\n case Movement.Up:\n P.y = Math.min(P.y + P.speed, this.maxY - P.height / 2)\n break\n case Movement.Down:\n P.y = Math.max(P.y - P.speed, -this.maxY + P.height / 2)\n break\n }\n }\n\n syncState () {\n this.renderer.setOldPositions(this.state.ball.oldPositions)\n this.renderer.setGameStage(this.stage)\n this.renderer.setLeftPaddlePosition(this.state.P1.x, this.state.P1.y)\n this.renderer.setRightPaddlePosition(this.state.P2.x, this.state.P2.y)\n this.renderer.setBallPosition(this.state.ball.x, this.state.ball.y)\n this.renderer.setScore(this.state.P1.score, this.state.P2.score)\n }\n\n draw () {\n this.renderer.draw()\n }\n\n addEventListeners () {\n this.canvas.addEventListener('mousedown', (e) => {\n const hitboxSize = 50\n const click = getCursorPosition(this.canvas, e)\n\n if (Math.abs(this.center.x - click.x) < hitboxSize &&\n Math.abs(this.center.y - click.y) < hitboxSize &&\n this.stage !== GameStage.Playing) {\n this.state = new State()\n this.stage = GameStage.Playing\n }\n })\n\n document.addEventListener('keydown', (k) => {\n switch (k.code) {\n case 'ArrowUp':\n this.state.P2.direction = Movement.Up\n break\n case 'ArrowDown':\n this.state.P2.direction = Movement.Down\n break\n case 'KeyW':\n this.state.P1.direction = Movement.Up\n break\n case 'KeyS':\n this.state.P1.direction = Movement.Down\n break\n }\n })\n\n document.addEventListener('keyup', (k) => {\n switch (k.code) {\n case 'ArrowUp':\n if (this.state.P2.direction === Movement.Up) {\n this.state.P2.direction = Movement.None\n }\n break\n case 'ArrowDown':\n if (this.state.P2.direction === Movement.Down) {\n this.state.P2.direction = Movement.None\n }\n break\n case 'KeyW':\n if (this.state.P1.direction === Movement.Up) {\n this.state.P1.direction = Movement.None\n }\n break\n case 'KeyS':\n if (this.state.P1.direction === Movement.Down) {\n this.state.P1.direction = Movement.None\n }\n break\n }\n })\n }\n}\n\n// https://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element\nfunction getCursorPosition (canvas, event) {\n const rect = canvas.getBoundingClientRect()\n const x = event.clientX - rect.left\n const y = event.clientY - rect.top\n return { x: x, y: y }\n}\n\nclass State {\n P1\n P2\n ball\n terminating\n\n constructor () {\n this.ball = new Ball()\n this.P1 = new Player(-0.9)\n this.P2 = new Player(0.9)\n this.terminating = false\n }\n\n gameOver () {\n return this.P1.score > 9 || this.P2.score > 9\n }\n\n currentStage () {\n if (this.P1.score >= 9) {\n return GameStage.P1Win\n } else if (this.P2.score >= 9) {\n return GameStage.P2Win\n }\n return GameStage.Playing\n }\n}\n","export default class Shader {\n gl\n program\n\n // constructor (gl, vsSource, fsSource, sourceVars) {\n constructor (gl, vsSource, fsSource) {\n this.gl = gl\n\n // preprocess sources\n // vsSource = this.preprocessSource(vsSource, sourceVars)\n // fsSource = this.preprocessSource(fsSource, sourceVars)\n\n this.program = this.initShaderProgram(gl, vsSource, fsSource)\n }\n\n //\n // Initialize a shader program, so WebGL knows how to draw our data\n //\n initShaderProgram (gl, vsSource, fsSource) {\n const vertexShader = this.loadShader(gl, gl.VERTEX_SHADER, vsSource)\n const fragmentShader = this.loadShader(gl, gl.FRAGMENT_SHADER, fsSource)\n\n // Create the shader program\n const shaderProgram = gl.createProgram()\n gl.attachShader(shaderProgram, vertexShader)\n gl.attachShader(shaderProgram, fragmentShader)\n gl.linkProgram(shaderProgram)\n\n // If creating the shader program failed, alert\n if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {\n alert(`Unable to initialize the shader program: ${gl.getProgramInfoLog(shaderProgram)}`)\n return null\n }\n\n return shaderProgram\n }\n\n //\n // preprocesses our shader source files\n // it replaces variables in the format 42//$VARIABLE_NAME$// with the VARIABLE_NAME\n // item from the sourceVars object\n //\n preprocessSource (source, sourceVars) {\n const sourceVarPattern = /42\\/\\/\\$(.*)\\$\\/\\//g\n return source.replace(sourceVarPattern, (_, varName) => sourceVars[varName])\n }\n\n //\n // creates a shader of the given type, uploads the source and\n // compiles it.\n //\n loadShader (gl, type, source) {\n const shader = gl.createShader(type)\n\n // Send the source to the shader object\n gl.shaderSource(shader, source)\n\n // Compile the shader program\n gl.compileShader(shader)\n\n // See if it compiled successfully\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n console.error(`An error occurred compiling the shaders:\\n${gl.getShaderInfoLog(shader)}`)\n gl.deleteShader(shader)\n return null\n }\n\n return shader\n }\n\n use () {\n this.gl.useProgram(this.program)\n }\n\n setUniform1f (name, value) {\n this.gl.uniform1f(this.getUniformLocation(name), value)\n }\n\n setUniformMatrix4fv (name, value) {\n this.gl.uniformMatrix4fv(this.getUniformLocation(name), false, value)\n }\n\n setUniform2fv (name, value) {\n this.gl.uniform2fv(this.getUniformLocation(name), value)\n }\n\n setUniform3fv (name, value) {\n this.gl.uniform3fv(this.getUniformLocation(name), value)\n }\n\n getUniformLocation (name) {\n // TODO: Cache uniform locations\n return this.gl.getUniformLocation(this.program, name)\n }\n\n getAttribLocation (name) {\n // TODO: Cache attrib locations\n return this.gl.getAttribLocation(this.program, name)\n }\n\n delete () {\n this.gl.deleteProgram(this.program)\n }\n}\n","export default \"precision highp float;\\n\\nvarying mediump vec2 screenPosition;\\n\\n//\\n// GAME STATE\\n//\\nuniform vec2 score;\\nuniform vec2 ballPosition;\\nuniform vec2 leftPaddlePosition;\\nuniform vec2 rightPaddlePosition;\\nuniform float gameStage;\\nuniform vec2 oldPositions[30];\\nuniform float time;\\n\\n//\\n// CURVE\\n//\\nvec2 curveAmount = vec2(0.8, 0.6);\\nfloat caseBorder = 0.0125;\\n\\n\\nfloat ballRadius = 0.02;\\nvec2 paddleSize = vec2(0.04, 0.24);\\n\\n\\n//\\n// DRAWING FUNCTIONS\\n//\\nbool isInRect(vec2 position, vec2 center, vec2 size)\\n{\\n vec2 delta = abs(center - position); // delta for paddle 0\\n return delta.x < (size.x/2.0) && delta.y < (size.y/2.0);\\n}\\n\\nfloat pixelSize = 0.03;\\n\\n// xxx\\n// x x\\n// x x\\n// x x\\n// xxx\\nbool is0(vec2 position, vec2 pos)\\n{\\n return\\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 4.0)) ||\\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 4.0));\\n}\\n\\n\\n// x\\n// x\\n// x\\n// x\\n// x\\nbool is1(vec2 position, vec2 pos)\\n{\\n return isInRect(position, pos, vec2(pixelSize, pixelSize * 5.0));\\n}\\n\\n// xxx\\n// x\\n// xxx\\n// x\\n// xxx\\nbool is2(vec2 position, vec2 pos)\\n{\\n return\\n isInRect(position, pos, vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y + 1.0 * pixelSize), vec2(pixelSize, pixelSize)) ||\\n isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y - 1.0 * pixelSize), vec2(pixelSize, pixelSize));\\n}\\n\\n// xxx\\n// x\\n// xxx\\n// x\\n// xxx\\nbool is3(vec2 position, vec2 pos)\\n{\\n return\\n isInRect(position, pos, vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x + pixelSize, pos.y), vec2(pixelSize, 3.0 * pixelSize));\\n}\\n\\n// x x\\n// x x\\n// xxx\\n// x\\n// x\\nbool is4(vec2 position, vec2 pos)\\n{\\n return\\n isInRect(position, pos, vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x - pixelSize, pos.y + pixelSize), vec2(pixelSize, 3.0 * pixelSize)) ||\\n isInRect(position, vec2(pos.x + pixelSize, pos.y), vec2(pixelSize, 5.0 * pixelSize));\\n}\\n\\n// xxx\\n// x\\n// xxx\\n// x\\n// xxx\\nbool is5(vec2 position, vec2 pos)\\n{\\n return\\n isInRect(position, pos, vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y + 1.0 * pixelSize), vec2(pixelSize, pixelSize)) ||\\n isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y - 1.0 * pixelSize), vec2(pixelSize, pixelSize));\\n}\\n\\n// xxx\\n// x\\n// xxx\\n// x x\\n// xxx\\nbool is6(vec2 position, vec2 pos)\\n{\\n return\\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 4.0)) ||\\n isInRect(position, vec2(pos.x, pos.y), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y - 1.0 * pixelSize), vec2(pixelSize, pixelSize * 3.0));\\n}\\n\\n// xxx\\n// x\\n// x\\n// x\\n// x\\nbool is7(vec2 position, vec2 pos)\\n{\\n return\\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 4.0));\\n}\\n\\n// xxx\\n// x x\\n// xxx\\n// x x\\n// xxx\\nbool is8(vec2 position, vec2 pos)\\n{\\n return\\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 3.0)) ||\\n isInRect(position, vec2(pos.x, pos.y), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 3.0));\\n}\\n\\n// xxx\\n// x x\\n// xxx\\n// x\\n// xxx\\nbool is9(vec2 position, vec2 pos)\\n{\\n return\\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x - 1.0 * pixelSize, pos.y + 1.0 * pixelSize), vec2(pixelSize, pixelSize * 3.0)) ||\\n isInRect(position, vec2(pos.x, pos.y), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x + 1.0 * pixelSize, pos.y), vec2(pixelSize, pixelSize * 4.0));\\n}\\n\\n// xxx\\n// x x\\n// xxx\\n// x\\n// x\\nbool isP(vec2 position, vec2 pos)\\n{\\n return\\n isInRect(position, vec2(pos.x - pixelSize, pos.y), vec2(pixelSize, 5.0 * pixelSize)) ||\\n isInRect(position, pos, vec2(pixelSize, pixelSize)) ||\\n isInRect(position, vec2(pos.x + pixelSize, pos.y + pixelSize), vec2(pixelSize, 3.0 * pixelSize)) ||\\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize, pixelSize));\\n}\\n\\nbool isO(vec2 position, vec2 pos)\\n{\\n return is0(position, pos);\\n}\\n\\n// xxx\\n// x x\\n// x x\\n// x x\\n// x x\\nbool isN(vec2 position, vec2 pos)\\n{\\n return\\n isInRect(position, vec2(pos.x - pixelSize, pos.y), vec2(pixelSize, 5.0 * pixelSize)) ||\\n isInRect(position, vec2(pos.x + pixelSize, pos.y), vec2(pixelSize, 5.0 * pixelSize)) ||\\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(3.0 * pixelSize, pixelSize));\\n}\\n\\n\\n// xxx\\n// x\\n// x|x\\n// x x\\n// xxx\\nbool isG(vec2 position, vec2 pos)\\n{\\n return\\n isInRect(position, vec2(pos.x, pos.y + 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize * 3.0, pixelSize)) ||\\n isInRect(position, vec2(pos.x - pixelSize, pos.y), vec2(pixelSize, pixelSize * 3.0)) ||\\n isInRect(position, vec2(pos.x + pixelSize, pos.y - pixelSize), vec2(pixelSize)) ||\\n isInRect(position, vec2(pos.x + 0.75 * pixelSize, pos.y), vec2(1.5 * pixelSize, pixelSize));\\n}\\n\\n// x\\n// x\\n// x\\n//\\n// x\\nbool isExclamationMark(vec2 position, vec2 pos)\\n{\\n return\\n isInRect(position, vec2(pos.x, pos.y + pixelSize), vec2(pixelSize, pixelSize * 3.0)) ||\\n isInRect(position, vec2(pos.x, pos.y - 2.0 * pixelSize), vec2(pixelSize));\\n}\\n\\n\\nbool isPONG(vec2 position, vec2 pos)\\n{\\n return\\n isP(position, vec2(pos.x - 6.0 * pixelSize, pos.y)) ||\\n isO(position, vec2(pos.x - 2.0 * pixelSize, pos.y)) ||\\n isN(position, vec2(pos.x + 2.0 * pixelSize, pos.y)) ||\\n isG(position, vec2(pos.x + 6.0 * pixelSize, pos.y));\\n}\\n\\n\\nbool isTriangle(vec2 position, vec2 size, vec2 pos)\\n{\\n float top = pos.y + size.y;\\n float bottom = pos.y - size.y;\\n float start = pos.x;\\n float end = pos.x + size.x;\\n\\n float slope = size.y/size.x;\\n float relativeX = position.x - start;\\n\\n return\\n position.x > start && position.x < end && // x within triangle\\n (position.y < top - slope * relativeX && position.y > bottom + slope * relativeX); // y within triangle\\n\\n}\\n\\nbool isNumber(float number, vec2 position, vec2 numberPosition)\\n{ // GLSL is a beautiful language\\n if (number == 0.0) { return is0(position, numberPosition); }\\n if (number == 1.0) { return is1(position, numberPosition); }\\n if (number == 2.0) { return is2(position, numberPosition); }\\n if (number == 3.0) { return is3(position, numberPosition); }\\n if (number == 4.0) { return is4(position, numberPosition); }\\n if (number == 5.0) { return is5(position, numberPosition); }\\n if (number == 6.0) { return is6(position, numberPosition); }\\n if (number == 7.0) { return is7(position, numberPosition); }\\n if (number == 8.0) { return is8(position, numberPosition); }\\n if (number == 9.0) { return is9(position, numberPosition); }\\n return false;\\n}\\n\\nbool isP1score(vec2 position) {\\n return isNumber(score.x, position, vec2(-4.0 * pixelSize, 18.0 * pixelSize));\\n}\\n\\nbool isP2score(vec2 position) {\\n return isNumber(score.y, position, vec2(4.0 * pixelSize, 18.0 * pixelSize));\\n}\\n\\nbool isLine(vec2 position) {\\n return\\n abs(position.x) < pixelSize / 2.0 &&\\n mod(position.y - pixelSize * 1.5, pixelSize * 2.0) <= pixelSize;\\n}\\n\\nbool isWinLine(vec2 position) {\\n return isLine(position) &&\\n (position.y > pixelSize * 3.0 || position.y < pixelSize * -3.0);\\n}\\n\\nbool isPlayButton(vec2 position) {\\n return isTriangle(position, vec2(0.16, 0.1), vec2(-0.06, 0));\\n}\\n\\n//\\n// GAMESTAGE SPECIFIC RENDERING\\n//\\nvec4 welcome(vec2 position) { // GameStage.Welcome\\n if (isPONG(position, vec2(0, 0.3)) ||\\n isPlayButton(position))\\n {\\n return vec4(1, 1, 1, 1);\\n }\\n return vec4(0, 0, 0, 1.0);\\n}\\n\\nvec4 playing(vec2 position) // GameStage.Playing\\n{\\n bool isLeftPaddle = isInRect(position, leftPaddlePosition, paddleSize);\\n bool isRightPaddle = isInRect(position, rightPaddlePosition, paddleSize);\\n bool isBall = isInRect(position, ballPosition, vec2(ballRadius * 2.0, ballRadius * 2.0));\\n\\n if (isLeftPaddle ||\\n isRightPaddle ||\\n isBall ||\\n isLine(position) ||\\n isP1score(position) ||\\n isP2score(position))\\n {\\n return vec4(1, 1, 1, 1);\\n }\\n\\n // Render tail\\n for (int i = 29; i >= 0; i--) {\\n vec2 oldPos = oldPositions[i];\\n float scale = float(i) / 30.0;\\n if (isInRect(position, oldPos, vec2(ballRadius * 2.0 * scale))) {\\n return vec4(scale, scale, scale, 1.0);\\n }\\n }\\n\\n return vec4(0, 0, 0, 1.0);\\n}\\n\\nvec4 p1win(vec2 position) // GameStage.P1Win\\n{\\n if (isP(position, vec2(-19.0 * pixelSize, 0)) ||\\n is1(position, vec2(-16.0 * pixelSize, 0)) ||\\n isExclamationMark(position, vec2(-14.0 * pixelSize, 0)) ||\\n isP1score(position) ||\\n isP2score(position) ||\\n isWinLine(position) ||\\n isPlayButton(position)\\n )\\n {\\n return vec4(1, 1, 1, 1);\\n }\\n return vec4(0, 0, 0, 1.0);\\n}\\n\\nvec4 p2win(vec2 position) // GameStage.P2Win\\n{\\n if (isP(position, vec2(14.0 * pixelSize, 0)) ||\\n is2(position, vec2(18.0 * pixelSize, 0)) ||\\n isExclamationMark(position, vec2(21.0 * pixelSize, 0)) ||\\n isP1score(position) ||\\n isP2score(position) ||\\n isWinLine(position) ||\\n isPlayButton(position)\\n )\\n {\\n return vec4(1, 1, 1, 1);\\n }\\n return vec4(0, 0, 0, 1.0);\\n}\\n\\nvec4 scanline(vec4 color, vec2 position) {\\n float lineHeight = 0.0104;\\n float darkness = 0.9;\\n float speed = 0.0104;\\n\\n float y = mod(position.y + (time / 60.0) * speed, lineHeight);\\n if (y < lineHeight/2.0) {\\n return color;\\n } else {\\n return vec4(color.x, color.y, color.z, darkness);\\n }\\n}\\n\\nvec4 colorAt(vec2 position) {\\n // Map position from [0, 1] to [-1, 1] to get world space color\\n position *= 2.0;\\n position -= vec2(1.0);\\n\\n vec4 color;\\n\\n if (abs(position.y) > 0.666) { // out of render area\\n return vec4(0, 0, 0, 1);\\n }\\n\\n if (gameStage == 0.0) { // GameStage.Welcome\\n color = welcome(position);\\n } else if (gameStage == 1.0) { // GameStage.Playing\\n color = playing(position);\\n } else if (gameStage == 2.0) { // GameStage.P1Win\\n color = p1win(position);\\n } else if (gameStage == 3.0) { // GameStage.P2Win\\n color = p2win(position);\\n }\\n\\n // Apply scanline effect\\n color = scanline(color, position);\\n\\n return color;\\n}\\n\\n\\nvec4 curve(vec2 position) {\\n // Map position from [-1, 1] to [0, 1] for curve effect\\n position += vec2(1.0);\\n position /= 2.0;\\n\\n // Using this https://github.com/wessles/GLSL-CRT/blob/master/shader.frag algorithm\\n float dx = abs(0.5 - position.x);\\n float dy = abs(0.5 - position.y);\\n dx *= dx;\\n dy *= dy;\\n\\n position.x -= 0.5;\\n position.x *= 1.0 + (dy * curveAmount.x);\\n position.x += 0.5;\\n\\n position.y -= 0.5;\\n position.y *= 1.0 + (dx * curveAmount.y);\\n position.y += 0.5;\\n\\n // Draw color from world space\\n vec4 color = colorAt(position);\\n color += sin(position.y) * 0.02;\\n\\n if(position.y > 1.0 || position.x < 0.0 || position.x > 1.0 || position.y < 0.0)\\n color = vec4(0, 0, 0, 1);\\n\\n return color;\\n}\\n\\nvoid main() {\\n gl_FragColor = curve(screenPosition);\\n}\\n\";","export default \"precision highp float;\\n\\nattribute vec4 aVertexPosition;\\n\\nuniform mat4 uModelViewMatrix;\\nuniform mat4 uProjectionMatrix;\\n\\nvarying mediump vec2 screenPosition;\\n\\nvoid main() {\\n screenPosition = aVertexPosition.xy;\\n gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;\\n}\\n\";","import { mat4 } from 'gl-matrix'\nimport Shader from './shader'\n\nimport fsSource from './shaders/shader.frag'\nimport vsSource from './shaders/shader.vert'\n\nexport default class PongRenderer {\n gl\n shader\n buffers\n\n // constructor (gl, shaderSourceVars) {\n constructor (gl) {\n this.gl = gl\n this.shader = new Shader(gl, vsSource, fsSource)\n this.buffers = this.initBuffers()\n\n this.shader.use()\n this.setDefaultPositions()\n }\n\n recompileShader (shaderSourceVars) {\n this.shader.delete()\n this.shader = new Shader(this.gl, vsSource, fsSource, shaderSourceVars)\n }\n\n initBuffers () {\n // Create a buffer for the square's positions.\n\n const positionBuffer = this.gl.createBuffer()\n\n // Select the positionBuffer as the one to apply buffer\n // operations to from here out.\n\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, positionBuffer)\n\n // Now create an array of positions for the square.\n\n const positions = [\n -1.0, 1.0,\n 1.0, 1.0,\n -1.0, -1.0,\n 1.0, -1.0\n ]\n\n // Now pass the list of positions into WebGL to build the\n // shape. We do this by creating a Float32Array from the\n // JavaScript array, then use it to fill the current buffer.\n this.gl.bufferData(this.gl.ARRAY_BUFFER,\n new Float32Array(positions),\n this.gl.STATIC_DRAW)\n\n return {\n position: positionBuffer\n }\n }\n\n draw () {\n this.gl.clearColor(0.0, 0.0, 0.0, 1.0) // Clear to black, fully opaque\n this.gl.clear(this.gl.COLOR_BUFFER_BIT)\n this.gl.clearDepth(1.0) // Clear everything\n this.gl.enable(this.gl.DEPTH_TEST) // Enable depth testing\n this.gl.depthFunc(this.gl.LEQUAL) // Near things obscure far things\n\n // Clear the canvas before we start drawing on it.\n\n this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT)\n\n // Create a perspective matrix, a special matrix that is\n // used to simulate the distortion of perspective in a camera.\n const fieldOfView = 29.8 * Math.PI / 180 // in radians\n const aspect = this.gl.canvas.clientWidth / this.gl.canvas.clientHeight\n const zNear = 0.1\n const zFar = 100.0\n const projectionMatrix = mat4.create()\n\n // gl-matrix.js always has the first argument as the destination to receive the result.\n mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar)\n\n // Set the drawing position to the \"identity\" point, which is\n // the center of the scene.\n const modelViewMatrix = mat4.create()\n\n // Now move the drawing position a bit to where we want to\n // start drawing the square.\n mat4.translate(modelViewMatrix, // destination matrix\n modelViewMatrix, // matrix to translate\n [0.0, 0.0, -2.5]) // amount to translate\n\n // Tell WebGL how to pull out the positions from the position\n // buffer into the vertexPosition attribute.\n const vertexPositionLocation = this.shader.getAttribLocation('aVertexPosition')\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.position)\n this.gl.vertexAttribPointer(vertexPositionLocation, 2, this.gl.FLOAT, false, 0, 0)\n this.gl.enableVertexAttribArray(vertexPositionLocation)\n\n // Tell WebGL to use our program when drawing\n this.shader.use()\n\n // Set the shader uniforms\n this.shader.setUniformMatrix4fv('uProjectionMatrix', projectionMatrix)\n this.shader.setUniformMatrix4fv('uModelViewMatrix', modelViewMatrix)\n {\n const offset = 0\n const vertexCount = 4\n this.gl.drawArrays(this.gl.TRIANGLE_STRIP, offset, vertexCount)\n }\n }\n\n setOldPositions (oldPositions) {\n for (const [i, pos] of oldPositions.entries()) {\n this.shader.setUniform2fv(`oldPositions[${i}]`, pos)\n }\n }\n\n setTime (time) {\n this.shader.setUniform1f('time', time)\n }\n\n setGameStage (gameStage) {\n this.shader.setUniform1f('gameStage', gameStage)\n }\n\n setDefaultPositions () {\n this.setLeftPaddlePosition(-0.9, 0)\n this.setRightPaddlePosition(0.9, 0)\n this.setBallPosition(0, 0)\n }\n\n setLeftPaddlePosition (x, y) {\n this.shader.setUniform2fv('leftPaddlePosition', new Float32Array([x, y]))\n }\n\n setRightPaddlePosition (x, y) {\n this.shader.setUniform2fv('rightPaddlePosition', new Float32Array([x, y]))\n }\n\n setBallPosition (x, y) {\n this.shader.setUniform2fv('ballPosition', new Float32Array([x, y]))\n }\n\n setScore (x, y) {\n this.shader.setUniform2fv('score', new Float32Array([x, y]))\n }\n}\n","\n\n\n\n\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Home.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Home.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./Home.vue?vue&type=template&id=6ea6888c&\"\nimport script from \"./Home.vue?vue&type=script&lang=js&\"\nexport * from \"./Home.vue?vue&type=script&lang=js&\"\nimport style0 from \"./Home.vue?vue&type=style&index=0&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","import Vue from 'vue'\nimport VueRouter from 'vue-router'\nimport Home from '../views/Home.vue'\n\nVue.use(VueRouter)\n\nconst routes = [\n {\n path: '/',\n name: 'Home',\n component: Home\n },\n {\n path: '/about',\n name: 'About',\n // route level code-splitting\n // this generates a separate chunk (about.[hash].js) for this route\n // which is lazy-loaded when the route is visited.\n component: () => import(/* webpackChunkName: \"about\" */ '../views/About.vue')\n }\n]\n\nconst router = new VueRouter({\n mode: 'history',\n base: process.env.BASE_URL,\n routes\n})\n\nexport default router\n","import Vue from 'vue'\nimport Vuex from 'vuex'\n\nVue.use(Vuex)\n\nexport default new Vuex.Store({\n state: {\n },\n mutations: {\n },\n actions: {\n },\n modules: {\n }\n})\n","import Vue from 'vue'\nimport App from './App.vue'\nimport './registerServiceWorker'\nimport router from './router'\nimport store from './store'\n\nVue.config.productionTip = false\n\nnew Vue({\n router,\n store,\n render: h => h(App)\n}).$mount('#app')\n","import mod from \"-!../node_modules/mini-css-extract-plugin/dist/loader.js??ref--8-oneOf-1-0!../node_modules/css-loader/dist/cjs.js??ref--8-oneOf-1-1!../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../node_modules/postcss-loader/src/index.js??ref--8-oneOf-1-2!../node_modules/sass-loader/dist/cjs.js??ref--8-oneOf-1-3!../node_modules/cache-loader/dist/cjs.js??ref--0-0!../node_modules/vue-loader/lib/index.js??vue-loader-options!./App.vue?vue&type=style&index=0&lang=scss&\"; export default mod; export * from \"-!../node_modules/mini-css-extract-plugin/dist/loader.js??ref--8-oneOf-1-0!../node_modules/css-loader/dist/cjs.js??ref--8-oneOf-1-1!../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../node_modules/postcss-loader/src/index.js??ref--8-oneOf-1-2!../node_modules/sass-loader/dist/cjs.js??ref--8-oneOf-1-3!../node_modules/cache-loader/dist/cjs.js??ref--0-0!../node_modules/vue-loader/lib/index.js??vue-loader-options!./App.vue?vue&type=style&index=0&lang=scss&\"","module.exports = __webpack_public_path__ + \"media/score.63ce9ecd.wav\";","module.exports = __webpack_public_path__ + \"media/hit.f8648124.wav\";","module.exports = __webpack_public_path__ + \"media/side.f37fcc5f.wav\";","module.exports = __webpack_public_path__ + \"media/start.1f4848ab.wav\";"],"sourceRoot":""} --------------------------------------------------------------------------------