Last active
July 18, 2021 19:15
-
-
Save guiseek/a3d2704ead308a12edbcde71535fe01a to your computer and use it in GitHub Desktop.
My City App
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<mat-sidenav-container class="sidenav-container" [hasBackdrop]="(isHandset$ | async) === true"> | |
<mat-sidenav #drawer class="sidenav" fixedInViewport [attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'" | |
[mode]="(isHandset$ | async) ? 'over' : 'side'" [opened]="(isHandset$ | async) === false"> | |
<div class="tool-list"> | |
<my-city-tool-items> | |
<div myCityToolItem role="listitem"> | |
<my-city-tool-item *ngFor="let toolItem of toolsList" [tool]="toolItem" | |
(selectionChange)="onToolChanged($event)"> | |
</my-city-tool-item> | |
</div> | |
</my-city-tool-items> | |
</div> | |
</mat-sidenav> | |
<mat-sidenav-content class="sidenav-content"> | |
<mat-toolbar color="primary"> | |
<button type="button" mat-icon-button aria-label="Toggle sidenav" (click)="drawer.toggle()"> | |
<mat-icon aria-label="Side nav toggle icon"> | |
{{ drawer.opened ? 'menu_open' : 'menu' }} | |
</mat-icon> | |
</button> | |
<span class="spacer"></span> | |
<span>{{ title }}</span> | |
</mat-toolbar> | |
<section id="main"> | |
<!-- <div class="perpective"> | |
<div class="tardis-wrap"> | |
<div class="tardis"> | |
</div> | |
</div> | |
</div> --> | |
<div id="area"> | |
<canvas id="bg"></canvas> | |
<canvas id="fg"></canvas> | |
</div> | |
<button class="renew" mat-fab color="primary" (click)="renew()"> | |
<mat-icon>autorenew</mat-icon> | |
</button> | |
<button class="demo-city" mat-fab (click)="example()"> | |
<mat-icon>location_city</mat-icon> | |
</button> | |
</section> | |
</mat-sidenav-content> | |
</mat-sidenav-container> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; | |
import { Component, OnInit } from '@angular/core'; | |
import { Observable } from 'rxjs'; | |
import { map, shareReplay } from 'rxjs/operators'; | |
import { fromBase64, getElement, toBase64 } from './app.helpers'; | |
import { tools } from './app.tools'; | |
import { CanvasColor, CityMap, TileMap } from './app.types'; | |
import { ToolItem, ToolItemPosition } from './components/tool-item/tool-item.component'; | |
@Component({ | |
selector: 'my-city-root', | |
templateUrl: './app.component.html', | |
styleUrls: ['./app.component.scss'], | |
}) | |
export class AppComponent implements OnInit { | |
title = 'Minha Cidade'; | |
canvas: HTMLCanvasElement; | |
bg: CanvasRenderingContext2D; | |
fg: HTMLCanvasElement; | |
cf: CanvasRenderingContext2D; | |
w: number; | |
h: number; | |
map: CityMap; | |
tool: ToolItemPosition = [0, 0]; | |
isPlacing: boolean; | |
tile: TileMap = { | |
width: 128, | |
height: 64, | |
base: 7 | |
}; | |
ntiles: number; | |
tileWidth: number; | |
tileHeight: number; | |
texWidth: number; | |
texHeight: number; | |
texture: HTMLImageElement; | |
toolsList: Array<ToolItem> = []; | |
isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset) | |
.pipe( | |
map(result => result.matches), | |
shareReplay() | |
); | |
constructor(private breakpointObserver: BreakpointObserver) { } | |
ngOnInit() { | |
this.texture = new Image() | |
this.texture.src = 'assets/textures/01_130x66_130x230.png' | |
this.texture.onload = _ => this.init() | |
const items = tools.length; | |
tools.map(tool => { | |
// console.log(tool); | |
}) | |
} | |
init() { | |
const canvas: HTMLCanvasElement = getElement('#bg'); | |
canvas.width = 910; | |
canvas.height = 666; | |
this.w = 910; | |
this.h = 462; | |
this.texWidth = 12; | |
this.texHeight = 6; | |
this.bg = canvas.getContext('2d'); | |
// this.tile.base = 7; | |
// this.tile.width = 128; | |
// this.tile.height = 64; | |
this.bg.translate(this.w / 2, this.tile.height * 2); | |
this.map = [ | |
[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], | |
[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], | |
[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], | |
[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], | |
[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], | |
[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], | |
[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], | |
]; | |
// const state = loadHashState( | |
// document.location.hash.substring(1), | |
// this.tile, | |
// this.texWidth | |
// ); | |
// console.log(state); | |
this.loadHashState(document.location.hash.substring(1)); | |
this.drawMap(); | |
this.fg = getElement('#fg'); | |
this.fg.width = canvas.width; | |
this.fg.height = canvas.height; | |
this.cf = this.fg.getContext('2d'); | |
this.cf.translate(this.w / 2, this.tile.height * 2); | |
// fromEvent(this.fg, 'mousemove') | |
// .pipe(debounceTime(500)) | |
// .subscribe((e: MouseEvent) => this.viz(e)); | |
this.fg.addEventListener('mousemove', this.viz); | |
this.fg.addEventListener('contextmenu', e => e.preventDefault()); | |
this.fg.addEventListener('mouseup', this.unclick); | |
this.fg.addEventListener('mousedown', this.click); | |
this.fg.addEventListener('touchend', this.click); | |
this.fg.addEventListener('pointerup', this.click); | |
// Map Rows | |
for (let i = 0; i < this.texHeight; i++) { | |
// Map Cols | |
for (let j = 0; j < this.texWidth; j++) { | |
// Tool Positions | |
this.toolsList.push({ position: [i, j] }); | |
} | |
} | |
} | |
onToolChanged({ position }: Pick<ToolItem, 'position'>) { | |
this.toolsList.forEach((t) => { t.active = false; }); | |
this.tool = position; | |
} | |
click = (e: MouseEvent) => { | |
// console.log(e); | |
const pos = this.getPosition(e); | |
if ( | |
pos.x >= 0 && | |
pos.x < this.tile.base && | |
pos.y >= 0 && | |
pos.y < this.tile.base | |
) { | |
this.map[pos.x][pos.y][0] = (e.which === 3) ? 0 : this.tool[0]; | |
this.map[pos.x][pos.y][1] = (e.which === 3) ? 0 : this.tool[1]; | |
this.isPlacing = true; | |
this.drawMap(); | |
this.cf.clearRect(-this.w, -this.h, this.w * 2, this.h * 2); | |
} | |
this.updateHashState(); | |
} | |
unclick = () => { | |
if (this.isPlacing) { | |
this.isPlacing = false; | |
} | |
} | |
drawMap = () => { | |
this.bg.clearRect( | |
-this.w, | |
-this.h, | |
this.w * 2, | |
this.h * 2 | |
); | |
for (let i = 0; i < this.tile.base; i++) { | |
for (let j = 0; j < this.tile.base; j++) { | |
this.drawImageTile(this.bg, i, j, this.map[i][j][0], this.map[i][j][1]); | |
} | |
} | |
} | |
drawTile = ( | |
c: CanvasRenderingContext2D, | |
x: number, y: number, | |
color: CanvasColor | |
) => { | |
c.save(); | |
c.translate((y - x) * this.tile.width / 2, (x + y) * this.tile.height / 2); | |
c.beginPath(); | |
c.moveTo(0, 0); | |
c.lineTo(this.tile.width / 2, this.tile.height / 2); | |
c.lineTo(0, this.tile.height); | |
c.lineTo(-this.tile.width / 2, this.tile.height / 2); | |
c.closePath(); | |
c.fillStyle = color; | |
c.fill(); | |
c.restore(); | |
} | |
drawImageTile = ( | |
c: CanvasRenderingContext2D, | |
x: number, | |
y: number, | |
i: number, | |
j: number | |
) => { | |
c.save(); | |
c.translate((y - x) * this.tile.width / 2, (x + y) * this.tile.height / 2); | |
j *= 130; | |
i *= 230; | |
c.drawImage(this.texture, j, i, 130, 230, -65, -130, 130, 230); | |
c.restore(); | |
} | |
getPosition = (e: MouseEvent) => { | |
const _y = (e.offsetY - this.tile.height * 2) / this.tile.height; | |
const _x = e.offsetX / this.tile.width - this.tile.base / 2; | |
const x = Math.floor(_y - _x); | |
const y = Math.floor(_x + _y); | |
return { x, y } | |
} | |
viz = (e: MouseEvent) => { | |
// console.log(e); | |
if (this.isPlacing) { this.click(e) }; | |
const pos = this.getPosition(e); | |
this.cf.clearRect(-this.w, -this.h, this.w * 2, this.h * 2); | |
if ( | |
pos.x >= 0 && | |
pos.x < this.tile.base && | |
pos.y >= 0 && | |
pos.y < this.tile.base | |
) { | |
this.drawTile(this.cf, pos.x, pos.y, 'rgba(0,0,0,0.2)'); | |
} | |
} | |
updateHashState = () => { | |
let c = 0; | |
const u8 = new Uint8Array(this.tile.base * this.tile.base); | |
for (let i = 0; i < this.tile.base; i++) { | |
for (let j = 0; j < this.tile.base; j++) { | |
u8[c++] = this.map[i][j][0] * this.texWidth + this.map[i][j][1]; | |
} | |
} | |
const state = toBase64(u8); | |
history.replaceState(undefined, undefined, `#${state}`); | |
} | |
loadHashState = (state: string) => { | |
const u8 = fromBase64(state); | |
let c = 0; | |
for (let i = 0; i < this.tile.base; i++) { | |
for (let j = 0; j < this.tile.base; j++) { | |
const t = u8[c++] || 0; | |
const x = Math.trunc(t / this.texWidth); | |
const y = Math.trunc(t % this.texWidth); | |
this.map[i][j] = [x, y]; | |
} | |
} | |
} | |
renew() { | |
window.location.href = '/'; | |
} | |
example() { | |
window.location.href = '/#LS8iREYAADYTBCkFDSU5BiYIDScAOAYBFTIOAC4EGA4xDgAAJhYoMCYlAAAAAAAAAA=='; | |
window.location.reload(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment