├── .browserslistrc ├── .eslintrc.js ├── .firebase └── hosting.ZGlzdA.cache ├── .firebaserc ├── .gitignore ├── README.md ├── babel.config.js ├── firebase.json ├── gol-engine.d.ts ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ ├── fonts.scss │ ├── style.scss │ ├── theme_love.scss │ └── theme_pastel.scss ├── components │ ├── Controls.vue │ ├── ModalPage.vue │ └── TopBar.vue ├── concerns │ └── store_concerns.ts ├── main.ts ├── router │ └── index.ts ├── rxjs │ ├── index.ts │ └── message.ts ├── shims-tsx.d.ts ├── shims-vue.d.ts ├── store │ └── index.ts └── views │ ├── About.vue │ ├── Gol.vue │ ├── Home.vue │ └── HowTo.vue ├── tsconfig.json ├── webpack.config.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended', 9 | '@vue/typescript/recommended' 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 2020 13 | }, 14 | rules: { 15 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.firebase/hosting.ZGlzdA.cache: -------------------------------------------------------------------------------- 1 | index.html,1587667340109,4544e65ee6f5a5bf08a790202c5b33f56a4b80ac1193426dfd474e10a4c4540c 2 | css/about.aeccb8a9.css,1587667340109,913dc54c8f3c18937a586c47738f5bed0a3d91e2c1c780e29b0c0d8e93a49ead 3 | favicon.ico,1587667340113,e0535b2041a7a1721cdec785c903980d41fdf0d810a4ea9726b6ffd1371bbc28 4 | css/app.59097944.css,1587667340113,e6deb471db3e8586526617225a76eeba8012c10292ee57e41276abc9fc5738b5 5 | css/chunk-vendors.d8f4d95f.css,1587667340113,60f15680001bc0350595af547ee970b62fb778d5e12ac92622429e555c697ed1 6 | css/gol.22c264af.css,1587667340113,20c163ee9597e5c0923e79f6c56217b1b90aca07079f4a23613f141dba02e532 7 | js/about.4b1a27b9.js,1587667340113,a9923bd168a9b45f1a9c30dc52253c0f9c258cf350eb48c00ee1e9edf7fc46aa 8 | js/app.b56a45b2.js,1587667340109,ccb9b07384dab46432b20ada94797e7456ea3f861427b887012648c7d41c1973 9 | js/app.b56a45b2.js.map,1587667340113,5901d29a4aecb68eba2ba599577c814496866e523f0920b87e8062114834224e 10 | js/gol.de140de1.js,1587667340113,e0ef7e7cba3594fdd4b7af7d41cf3dd1f62bd28ab0dc866247febecdb675d5aa 11 | js/howto.bc371b6e.js,1587667340113,c740ae41970b9f8b681ed49f51146f13453cb38985e4586c9f62eda3138eb919 12 | js/howto.bc371b6e.js.map,1587667340113,ab24c42114b0b845e049210e0e063c2bd68b079f49a1c86c647c082d15530a63 13 | js/about.4b1a27b9.js.map,1587667340113,d10641df7717cc0f630246122b2adb83212346cd330248dcf361d3bd27549c15 14 | js/gol.de140de1.js.map,1587667340113,805f989cb78ea3edaa9ad4d91a5a6420b3431d4e220ef4a99f088754c0371959 15 | js/chunk-vendors.da93afeb.js,1587667340113,7f6556a4a232a3f561704fe5b143ec76777208f8b41c563a22e2bb908cbcfe43 16 | js/chunk-vendors.da93afeb.js.map,1587667340113,ac150ff2d693a5e830626935d48bd34a44c6004d0b754f1500d8d1d23c622331 17 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "gameoflife-ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | 7 |

8 | 9 |

Click here to try it !

10 | 11 | # Features [![](https://img.shields.io/badge/autor-letItCurl-red.svg)](https://www.linkedin.com/in/roland-lopez-developer/?locale=en_US) 12 | 13 | - Drag and drop on desktop. 14 | - Hold touch event on mobile. 15 | - Infinite map generation. 16 | - Can spwan items during the playing action. 17 | - Map length resized in function of the view port. 18 | - Rewind generations. 19 | - Go backward and frontward step by step. 20 | 21 | # Technical stack 22 | - Vue.Js / Ts template 23 | - Canvas 24 | - VueX 25 | - RxJs 26 | - Vue touch events 27 | - Firebase Hosting 28 | 29 | # About 30 | Project made from scratch by me, for you, with <3. 31 | It uses a custom node_module, developed by me, coded also in Typescript. 32 | 33 | 34 | 39 | 48 | 49 |
35 |
36 | 37 |
38 |
40 |
41 |

Hey there !
42 | I'm letItCurl, fullstack developer engineer in freelance a.k.a Roland in real life :p
43 | If you have any question you can contact me if you wish !

44 |

I'm always ready to help !

45 | Email me 46 |
47 |
50 | 51 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "dist", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gol-engine.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'gol-engine'; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-gol-ts", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "^3.6.4", 12 | "node-sass": "^4.13.1", 13 | "rxjs": "^6.5.5", 14 | "sass-loader": "^8.0.2", 15 | "vue": "^2.6.11", 16 | "vue-class-component": "^7.2.3", 17 | "vue-property-decorator": "^8.4.1", 18 | "vue-router": "^3.1.6", 19 | "vue2-perfect-scrollbar": "^1.5.0", 20 | "vue2-touch-events": "^2.2.1", 21 | "vuedraggable": "^2.23.2", 22 | "vuex": "^3.1.3" 23 | }, 24 | "devDependencies": { 25 | "@typescript-eslint/eslint-plugin": "^2.26.0", 26 | "@typescript-eslint/parser": "^2.26.0", 27 | "@vue/cli-plugin-babel": "~4.3.0", 28 | "@vue/cli-plugin-eslint": "~4.3.0", 29 | "@vue/cli-plugin-router": "~4.3.0", 30 | "@vue/cli-plugin-typescript": "~4.3.0", 31 | "@vue/cli-plugin-vuex": "~4.3.0", 32 | "@vue/cli-service": "~4.3.0", 33 | "@vue/eslint-config-typescript": "^5.0.2", 34 | "eslint": "^6.7.2", 35 | "eslint-plugin-vue": "^6.2.2", 36 | "typescript": "~3.8.3", 37 | "vue-template-compiler": "^2.6.11" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letItCurl/vue-gol-ts/6b20eaf778c8701b6c47abb7cbc5d9807a829c82/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Game of life 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | > 13 | 14 | 20 | -------------------------------------------------------------------------------- /src/assets/fonts.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Comic+Neue:ital@1&family=Permanent+Marker&display=swap'); 2 | 3 | $marker: 'Permanent Marker', cursive; 4 | $comic: 'Comic Neue', cursive; -------------------------------------------------------------------------------- /src/assets/style.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Comic+Neue:ital@1&family=Permanent+Marker&display=swap'); 2 | @import '@/assets/theme_love.scss'; 3 | 4 | $marker: 'Permanent Marker', cursive; 5 | $comic: 'Comic Neue', cursive; 6 | 7 | body{ 8 | margin: 0; 9 | padding: 0; 10 | font-family: $marker; 11 | } 12 | 13 | ul{ 14 | list-style: none; 15 | margin: 0; 16 | padding: 0; 17 | p{ 18 | margin: 0; 19 | } 20 | } 21 | a{ 22 | text-decoration: none; 23 | outline: none; 24 | color: inherit; 25 | } 26 | svg{ 27 | color: $text-primary; 28 | width: 25px; 29 | height: 25px; 30 | transition: $speed; 31 | path{ 32 | margin: auto; 33 | } 34 | &:hover{ 35 | transform: scale(1.3); 36 | color: $active-color; 37 | } 38 | } -------------------------------------------------------------------------------- /src/assets/theme_love.scss: -------------------------------------------------------------------------------- 1 | $text-primary: #fab0ee; 2 | $text-secondary: rgb(255, 0, 242); 3 | $grey: rgba(255, 157, 255, 0.363); 4 | $bg-primary: rgba(96, 0, 70, 0.3); 5 | $bg-secondary: rgba(195, 0, 255, 0.308); 6 | $shadow: rgb(195, 0, 255); 7 | $speed: 600ms; 8 | 9 | $lines: #df00f3; 10 | $active-color: rgb(215, 255, 140); -------------------------------------------------------------------------------- /src/assets/theme_pastel.scss: -------------------------------------------------------------------------------- 1 | 2 | $text-primary: rgba(221, 97, 74, 1); 3 | $text-secondary: rgba(244, 134, 104, 1); 4 | $grey: rgb(141, 139, 94); 5 | $bg-primary: rgba(203, 118, 158, 0.548); 6 | $bg-secondary: rgba(115, 165, 128, 0.459); 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/components/Controls.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 65 | 66 | -------------------------------------------------------------------------------- /src/components/ModalPage.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | > 29 | 30 | 90 | -------------------------------------------------------------------------------- /src/components/TopBar.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 51 | 52 | -------------------------------------------------------------------------------- /src/concerns/store_concerns.ts: -------------------------------------------------------------------------------- 1 | export function backConcern(state: any, astroWorld: any){ 2 | const time = astroWorld.history.length; 3 | if(time > 1){ 4 | const back = astroWorld.history[time-2] 5 | state.map = [...back] 6 | astroWorld.map = [...back] 7 | astroWorld.history.pop() 8 | return true 9 | }else{ 10 | astroWorld.history.pop() 11 | return false 12 | } 13 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import Vue2TouchEvents from 'vue2-touch-events' 6 | import PerfectScrollbar from 'vue2-perfect-scrollbar' 7 | import 'vue2-perfect-scrollbar/dist/vue2-perfect-scrollbar.css' 8 | 9 | Vue.config.productionTip = false 10 | 11 | Vue.use(Vue2TouchEvents) 12 | Vue.use(PerfectScrollbar) 13 | 14 | new Vue({ 15 | router, 16 | store, 17 | render: h => h(App) 18 | }).$mount('#app') 19 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter, { RouteConfig } from 'vue-router' 3 | import Home from '../views/Home.vue' 4 | 5 | Vue.use(VueRouter) 6 | 7 | const routes: Array = [ 8 | /* 9 | { 10 | path: '/', 11 | name: 'Home', 12 | component: Home 13 | },*/ 14 | { 15 | path: '/', 16 | name: 'Gol', 17 | component: () => import(/* webpackChunkName: "gol" */ '../views/Gol.vue') 18 | }, 19 | { 20 | path: '/about', 21 | name: 'About', 22 | component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') 23 | }, 24 | { 25 | path: '/howto', 26 | name: 'HowTo', 27 | component: () => import(/* webpackChunkName: "howto" */ '../views/HowTo.vue') 28 | } 29 | ] 30 | 31 | const router = new VueRouter({ 32 | mode: 'history', 33 | base: process.env.BASE_URL, 34 | routes 35 | }) 36 | 37 | export default router 38 | -------------------------------------------------------------------------------- /src/rxjs/index.ts: -------------------------------------------------------------------------------- 1 | import {interval} from 'rxjs' 2 | 3 | export const flow = interval(100) 4 | 5 | -------------------------------------------------------------------------------- /src/rxjs/message.ts: -------------------------------------------------------------------------------- 1 | import { Subject } from 'rxjs'; 2 | 3 | const subject = new Subject(); 4 | 5 | export const dataBus = { 6 | sendMessage: (message: any) => subject.next({ message }), 7 | clearMessages: () => subject.next(), 8 | getMessage: () => subject.asObservable() 9 | }; -------------------------------------------------------------------------------- /src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import {world} from "gol-engine" 4 | import { flow } from '@/rxjs/index' 5 | import { backConcern } from '@/concerns/store_concerns' 6 | import { dataBus } from '@/rxjs/message'; 7 | 8 | 9 | var astroWorld = new world["default"](70); // eslint-disable-line 10 | 11 | astroWorld.initWorld() 12 | 13 | Vue.use(Vuex) 14 | 15 | export default new Vuex.Store({ 16 | state: { 17 | map: astroWorld.map, 18 | time: flow.subscribe(), 19 | mobileActive: 'barFive', 20 | size: 2000, 21 | grid: 70 22 | }, 23 | mutations: { 24 | NEXT(state){ 25 | astroWorld.nextDay() 26 | state.map = [...astroWorld.map] 27 | }, 28 | BACK(state){ 29 | backConcern(state, astroWorld) 30 | }, 31 | PLAY_PAUSE(state, payload){ 32 | if(!payload){ 33 | state.time = flow.subscribe(x=>{ 34 | astroWorld.nextDay() 35 | state.map = [...astroWorld.map] 36 | }) 37 | }else{ 38 | state.time.unsubscribe() 39 | } 40 | }, 41 | REWIND(state, payload){ 42 | if(!payload){ 43 | state.time = flow.subscribe(x=>{ 44 | if(!backConcern(state, astroWorld)){ 45 | state.time.unsubscribe() 46 | dataBus.sendMessage('UNLOCK'); 47 | } 48 | }) 49 | }else{ 50 | state.time.unsubscribe() 51 | } 52 | }, 53 | CLEAR(state){ 54 | astroWorld.clear() 55 | state.map = [...astroWorld.map] 56 | }, 57 | SET_BAR_FIVE(state, payload){ 58 | astroWorld.barFive(payload.X,payload.Y) 59 | state.map = [...astroWorld.map] 60 | }, 61 | SET_FROG(state, payload){ 62 | astroWorld.frog(payload.X,payload.Y) 63 | state.map = [...astroWorld.map] 64 | }, 65 | SET_GLIDER(state, payload){ 66 | astroWorld.glider(payload.X,payload.Y) 67 | state.map = [...astroWorld.map] 68 | }, 69 | SET_U_CLOWN(state, payload){ 70 | astroWorld.uClown(payload.X,payload.Y) 71 | state.map = [...astroWorld.map] 72 | }, 73 | MOBILE_ACTIVE(state, payload){ 74 | state.mobileActive = payload 75 | }, 76 | MOBILE_RESIZE(state, payload){ 77 | state.grid = payload.grid; 78 | state.size = payload.size; 79 | astroWorld = new world["default"](payload.grid); 80 | astroWorld.initWorld() 81 | } 82 | }, 83 | actions: { 84 | back(context): void { 85 | context.commit('BACK') 86 | }, 87 | playPause(context, payload): void { 88 | context.commit('PLAY_PAUSE', payload) 89 | }, 90 | next(context): void { 91 | context.commit('NEXT') 92 | }, 93 | clear(context): void{ 94 | context.commit('CLEAR') 95 | }, 96 | barFive(context, payload){ 97 | context.commit('SET_BAR_FIVE', payload) 98 | }, 99 | frog(context, payload){ 100 | context.commit('SET_FROG', payload) 101 | }, 102 | glider(context, payload){ 103 | context.commit('SET_GLIDER', payload) 104 | }, 105 | uClown(context, payload){ 106 | context.commit('SET_U_CLOWN', payload) 107 | }, 108 | rewind(context, payload){ 109 | context.commit('REWIND', payload) 110 | }, 111 | mobileActive(context, payload){ 112 | context.commit('MOBILE_ACTIVE', payload) 113 | } 114 | }, 115 | getters:{ 116 | map(state){ 117 | return state.map 118 | }, 119 | mobileActive(state){ 120 | return state.mobileActive 121 | }, 122 | size(state){ 123 | return state.size 124 | }, 125 | grid(state){ 126 | return state.grid 127 | } 128 | }, 129 | modules: { 130 | } 131 | }) 132 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 31 | 32 | 35 | -------------------------------------------------------------------------------- /src/views/Gol.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 110 | 126 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | -------------------------------------------------------------------------------- /src/views/HowTo.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "lib": [ 23 | "esnext", 24 | "dom", 25 | "dom.iterable", 26 | "scripthost" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.vue", 33 | "tests/**/*.ts", 34 | "tests/**/*.tsx" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | module: { 3 | rules: [ 4 | // ... other rules omitted 5 | 6 | // this will apply to both plain `.scss` files 7 | // AND `