Skip to content

Instantly share code, notes, and snippets.

@guiseek
Last active July 18, 2021 19:15
Show Gist options
  • Save guiseek/a3d2704ead308a12edbcde71535fe01a to your computer and use it in GitHub Desktop.
Save guiseek/a3d2704ead308a12edbcde71535fe01a to your computer and use it in GitHub Desktop.
My City App
<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>
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