├── .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://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 |
35 |
36 |
37 |
38 |
39 |
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 |
48 |
49 |
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 |
12 | We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
65 |
66 |
--------------------------------------------------------------------------------
/src/components/ModalPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
19 |
20 | >
29 |
30 |
90 |
--------------------------------------------------------------------------------
/src/components/TopBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
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 |
2 |
3 |
4 | wHAT IS THIS ??
5 |
6 | This game is a cellular automata named "the game of life"
7 | Basically a cell can survive, die or born based on the amount of neibourhoods.
8 | #click here for more info...
9 | Here is how to play:
10 | Select your partern above (Sword Frog Rocket or Alien) and click on the map to spawn paterns:
11 |
12 | Drag and drop availible on desktop! I disabled the feature on mobile because if you are not careful, you can refresh the page...
13 | You can play / pause the frames, go forward or backward of one frame or rewind all generations by double clicking ( holding in mobile ) the "back" button.
14 | Links:
15 |
16 |
17 |
18 |
19 |
20 |
31 |
32 |
35 |
--------------------------------------------------------------------------------
/src/views/Gol.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
110 |
126 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
20 |
--------------------------------------------------------------------------------
/src/views/HowTo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
This is an How to page
4 |
5 |
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 `