User prompt
GİRENLER HOŞGELDİN YAZISI GELE
User prompt
ET SES VER ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Arka plan et resimleriyle oluşmuş ve her tıklama şeyinde etler fırlasın ateş üzerinde olsun. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'length')' in or related to this line: 'var icon = self.attachAsset(self.iconId, {' Line Number: 75
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'length')' in or related to this line: 'var itemAsset = self.attachAsset(self.assetId, {' Line Number: 25
Code edit (1 edits merged)
Please save this source code
User prompt
Grill & Chill: Sizzling Idle Clicker
Initial prompt
Pişirmek için Tıkla - Et paraları kazanmak ve yeni cızırtılı sürprizlerin kilidini açmak için dokunun Sıcaklığı Yükseltin - Lezzetli araçlarla tıklama başına ve saniye başına jetonlarınızı artırın Insane Grillables - Dinamit, antik teknoloji ve daha fazlası gibi garip şeyleri kızartın Boşta Ödüller - Siz uzaktayken de kazanmaya devam edin (çünkü ızgara asla uyumaz) Oddly Relaxing - Basit, eğlenceli ve sonsuz tatmin edici
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { coins: 0, coinsPerTap: 1, coinsPerSec: 0, unlockedItems: [0], lastActive: undefined }); /**** * Classes ****/ // Grillable item class (the thing you tap) var GrillItem = Container.expand(function () { var self = Container.call(this); // Attach the current grillable asset var itemAsset = self.attachAsset(self.assetId, { anchorX: 0.5, anchorY: 0.5 }); // Sizzle effect on tap self.sizzle = function () { // Animate scale up and back tween(self, { scaleX: 1.15, scaleY: 1.15 }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); } }); // Flash briefly LK.effects.flashObject(self, 0xffe066, 120); }; // Change to a new asset (when new grillable unlocked) self.setAsset = function (assetId) { if (itemAsset) itemAsset.destroy(); self.assetId = assetId; var newAsset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); }; return self; }); // Upgrade button class var UpgradeButton = Container.expand(function () { var self = Container.call(this); // Button background var bg = self.attachAsset('upgradeBtnBg', { anchorX: 0.5, anchorY: 0.5 }); // Icon var icon = self.attachAsset(self.iconId, { anchorX: 0.5, anchorY: 0.5, x: 0, y: -30 }); // Label var label = new Text2(self.labelText, { size: 60, fill: "#fff" }); label.anchor.set(0.5, 0); label.y = 30; self.addChild(label); // Cost text var costTxt = new Text2('', { size: 48, fill: 0xFFE066 }); costTxt.anchor.set(0.5, 0); costTxt.y = 100; self.addChild(costTxt); self.setCost = function (cost) { costTxt.setText(cost + "🪙"); }; // Flash on purchase self.flash = function () { LK.effects.flashObject(self, 0x83de44, 200); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2d1b0e // Deep brown, grill vibes }); /**** * Game Code ****/ // Storage for idle coin accumulation and upgrades // Tween for upgrades and fun effects // --- Grillable Items Data --- var grillables = [ // id, asset config, display name, unlock cost { id: 'grillItem1', config: { width: 320, height: 320, color: 0xe25822, shape: 'ellipse' }, name: "Sausage", unlock: 0 }, { id: 'grillItem2', config: { width: 320, height: 320, color: 0x8e44ad, shape: 'box' }, name: "Purple Potato", unlock: 100 }, { id: 'grillItem3', config: { width: 320, height: 320, color: 0xcccccc, shape: 'box' }, name: "Old Phone", unlock: 500 }, { id: 'grillItem4', config: { width: 320, height: 320, color: 0xff0000, shape: 'ellipse' }, name: "Dynamite", unlock: 2000 }, { id: 'grillItem5', config: { width: 320, height: 320, color: 0x00e6e6, shape: 'ellipse' }, name: "Alien Egg", unlock: 10000 }]; // --- Upgrade Data --- var upgrades = [ // id, icon config, label, base cost, effect, effect per purchase { id: 'tap', icon: { width: 100, height: 100, color: 0xffc300, shape: 'ellipse' }, label: "Bigger Tongs", baseCost: 50, effect: "coinsPerTap", per: 1 }, { id: 'idle', icon: { width: 100, height: 100, color: 0x00b894, shape: 'box' }, label: "Auto Grill", baseCost: 200, effect: "coinsPerSec", per: 1 }]; // --- Asset Initialization --- for (var i = 0; i < grillables.length; i++) {} for (var i = 0; i < upgrades.length; i++) {} // --- State --- var coins = storage.coins || 0; var coinsPerTap = storage.coinsPerTap || 1; var coinsPerSec = storage.coinsPerSec || 0; var unlockedItems = storage.unlockedItems || [0]; // indices of grillables var lastActive = storage.lastActive || Date.now(); var currentItemIdx = unlockedItems[unlockedItems.length - 1]; // last unlocked var upgradeLevels = { tap: Math.floor((coinsPerTap - 1) / upgrades[0].per), idle: Math.floor(coinsPerSec / upgrades[1].per) }; // --- GUI Elements --- var coinTxt = new Text2('', { size: 120, fill: 0xFFE066 }); coinTxt.anchor.set(0.5, 0); LK.gui.top.addChild(coinTxt); var grillNameTxt = new Text2('', { size: 64, fill: "#fff" }); grillNameTxt.anchor.set(0.5, 0); grillNameTxt.y = 140; LK.gui.top.addChild(grillNameTxt); // --- Grill Item --- var grillItem = new GrillItem(); grillItem.assetId = grillables[currentItemIdx].id; grillItem.x = 2048 / 2; grillItem.y = 1200; game.addChild(grillItem); // --- Upgrades --- var upgradeBtns = []; for (var i = 0; i < upgrades.length; i++) { var upg = upgrades[i]; var btn = new UpgradeButton(); btn.iconId = 'upgrade_' + upg.id; btn.labelText = upg.label; btn.x = 2048 / 2 + (i === 0 ? -260 : 260); btn.y = 2200; btn.setCost(getUpgradeCost(upg.id)); game.addChild(btn); upgradeBtns.push(btn); } // --- Unlockable Items Buttons --- var unlockBtns = []; for (var i = 1; i < grillables.length; i++) { var g = grillables[i]; var btn = new Container(); var bg = btn.attachAsset('upgradeBtnBg', { anchorX: 0.5, anchorY: 0.5 }); var icon = btn.attachAsset(g.id, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); var costTxt = new Text2(g.unlock + "🪙", { size: 48, fill: 0xFFE066 }); costTxt.anchor.set(0.5, 0); costTxt.y = 80; btn.addChild(costTxt); btn.x = 2048 / 2 + (i - 2) * 220; btn.y = 1700; btn.grillIdx = i; game.addChild(btn); unlockBtns.push(btn); } // --- Helper Functions --- function getUpgradeCost(id) { if (id === 'tap') { return upgrades[0].baseCost * Math.pow(2, upgradeLevels.tap); } else if (id === 'idle') { return upgrades[1].baseCost * Math.pow(2, upgradeLevels.idle); } return 9999; } function updateCoinDisplay() { coinTxt.setText(coins + "🪙"); } function updateGrillName() { grillNameTxt.setText(grillables[currentItemIdx].name); } function updateUpgradeButtons() { for (var i = 0; i < upgradeBtns.length; i++) { var upg = upgrades[i]; upgradeBtns[i].setCost(getUpgradeCost(upg.id)); } } function updateUnlockBtns() { for (var i = 0; i < unlockBtns.length; i++) { var idx = unlockBtns[i].grillIdx; if (unlockedItems.indexOf(idx) !== -1) { unlockBtns[i].alpha = 0.3; } else { unlockBtns[i].alpha = 1; } } } // --- Idle Coin Calculation (on load) --- var now = Date.now(); if (coinsPerSec > 0 && lastActive && now > lastActive) { var secondsAway = Math.floor((now - lastActive) / 1000); var idleCoins = secondsAway * coinsPerSec; if (idleCoins > 0) { coins += idleCoins; // Fun effect: flash coin text LK.effects.flashObject(coinTxt, 0x83de44, 800); } } lastActive = now; storage.lastActive = now; // --- Save State Helper --- function saveState() { storage.coins = coins; storage.coinsPerTap = coinsPerTap; storage.coinsPerSec = coinsPerSec; storage.unlockedItems = unlockedItems; storage.lastActive = Date.now(); } // --- Main Tap Handler (grill tap) --- grillItem.down = function (x, y, obj) { coins += coinsPerTap; updateCoinDisplay(); grillItem.sizzle(); saveState(); }; // --- Upgrade Button Handlers --- for (var i = 0; i < upgradeBtns.length; i++) { (function (btn, upg) { btn.down = function (x, y, obj) { var cost = getUpgradeCost(upg.id); if (coins >= cost) { coins -= cost; if (upg.id === 'tap') { coinsPerTap += upg.per; upgradeLevels.tap++; } else if (upg.id === 'idle') { coinsPerSec += upg.per; upgradeLevels.idle++; } btn.flash(); updateCoinDisplay(); updateUpgradeButtons(); saveState(); } else { // Not enough coins: shake tween(btn, { x: btn.x - 20 }, { duration: 60, onFinish: function onFinish() { tween(btn, { x: btn.x + 40 }, { duration: 80, onFinish: function onFinish() { tween(btn, { x: btn.x - 20 }, { duration: 60 }); } }); } }); } }; })(upgradeBtns[i], upgrades[i]); } // --- Unlockable Grillable Handlers --- for (var i = 0; i < unlockBtns.length; i++) { (function (btn, idx) { btn.down = function (x, y, obj) { if (unlockedItems.indexOf(idx) !== -1) { // Already unlocked: switch to this grillable currentItemIdx = idx; grillItem.setAsset(grillables[idx].id); updateGrillName(); saveState(); return; } var cost = grillables[idx].unlock; if (coins >= cost) { coins -= cost; unlockedItems.push(idx); currentItemIdx = idx; grillItem.setAsset(grillables[idx].id); updateCoinDisplay(); updateUnlockBtns(); updateGrillName(); LK.effects.flashObject(btn, 0x83de44, 400); saveState(); } else { // Not enough coins: shake tween(btn, { x: btn.x - 20 }, { duration: 60, onFinish: function onFinish() { tween(btn, { x: btn.x + 40 }, { duration: 80, onFinish: function onFinish() { tween(btn, { x: btn.x - 20 }, { duration: 60 }); } }); } }); } }; })(unlockBtns[i], unlockBtns[i].grillIdx); } // --- Update GUI on load --- updateCoinDisplay(); updateUpgradeButtons(); updateUnlockBtns(); updateGrillName(); // --- Idle Coin Timer --- var idleTimer = LK.setInterval(function () { if (coinsPerSec > 0) { coins += coinsPerSec; updateCoinDisplay(); saveState(); } }, 1000); // --- Save last active time on pause/leave --- LK.on('pause', function () { storage.lastActive = Date.now(); }); // --- Game Update (not much needed) --- game.update = function () { // No per-frame logic needed for MVP };
===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,437 @@
-/****
+/****
+* Plugins
+****/
+var tween = LK.import("@upit/tween.v1");
+var storage = LK.import("@upit/storage.v1", {
+ coins: 0,
+ coinsPerTap: 1,
+ coinsPerSec: 0,
+ unlockedItems: [0],
+ lastActive: undefined
+});
+
+/****
+* Classes
+****/
+// Grillable item class (the thing you tap)
+var GrillItem = Container.expand(function () {
+ var self = Container.call(this);
+ // Attach the current grillable asset
+ var itemAsset = self.attachAsset(self.assetId, {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ // Sizzle effect on tap
+ self.sizzle = function () {
+ // Animate scale up and back
+ tween(self, {
+ scaleX: 1.15,
+ scaleY: 1.15
+ }, {
+ duration: 80,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ tween(self, {
+ scaleX: 1,
+ scaleY: 1
+ }, {
+ duration: 120,
+ easing: tween.easeIn
+ });
+ }
+ });
+ // Flash briefly
+ LK.effects.flashObject(self, 0xffe066, 120);
+ };
+ // Change to a new asset (when new grillable unlocked)
+ self.setAsset = function (assetId) {
+ if (itemAsset) itemAsset.destroy();
+ self.assetId = assetId;
+ var newAsset = self.attachAsset(assetId, {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ };
+ return self;
+});
+// Upgrade button class
+var UpgradeButton = Container.expand(function () {
+ var self = Container.call(this);
+ // Button background
+ var bg = self.attachAsset('upgradeBtnBg', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ // Icon
+ var icon = self.attachAsset(self.iconId, {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: 0,
+ y: -30
+ });
+ // Label
+ var label = new Text2(self.labelText, {
+ size: 60,
+ fill: "#fff"
+ });
+ label.anchor.set(0.5, 0);
+ label.y = 30;
+ self.addChild(label);
+ // Cost text
+ var costTxt = new Text2('', {
+ size: 48,
+ fill: 0xFFE066
+ });
+ costTxt.anchor.set(0.5, 0);
+ costTxt.y = 100;
+ self.addChild(costTxt);
+ self.setCost = function (cost) {
+ costTxt.setText(cost + "🪙");
+ };
+ // Flash on purchase
+ self.flash = function () {
+ LK.effects.flashObject(self, 0x83de44, 200);
+ };
+ return self;
+});
+
+/****
* Initialize Game
-****/
+****/
var game = new LK.Game({
- backgroundColor: 0x000000
-});
\ No newline at end of file
+ backgroundColor: 0x2d1b0e // Deep brown, grill vibes
+});
+
+/****
+* Game Code
+****/
+// Storage for idle coin accumulation and upgrades
+// Tween for upgrades and fun effects
+// --- Grillable Items Data ---
+var grillables = [
+// id, asset config, display name, unlock cost
+{
+ id: 'grillItem1',
+ config: {
+ width: 320,
+ height: 320,
+ color: 0xe25822,
+ shape: 'ellipse'
+ },
+ name: "Sausage",
+ unlock: 0
+}, {
+ id: 'grillItem2',
+ config: {
+ width: 320,
+ height: 320,
+ color: 0x8e44ad,
+ shape: 'box'
+ },
+ name: "Purple Potato",
+ unlock: 100
+}, {
+ id: 'grillItem3',
+ config: {
+ width: 320,
+ height: 320,
+ color: 0xcccccc,
+ shape: 'box'
+ },
+ name: "Old Phone",
+ unlock: 500
+}, {
+ id: 'grillItem4',
+ config: {
+ width: 320,
+ height: 320,
+ color: 0xff0000,
+ shape: 'ellipse'
+ },
+ name: "Dynamite",
+ unlock: 2000
+}, {
+ id: 'grillItem5',
+ config: {
+ width: 320,
+ height: 320,
+ color: 0x00e6e6,
+ shape: 'ellipse'
+ },
+ name: "Alien Egg",
+ unlock: 10000
+}];
+// --- Upgrade Data ---
+var upgrades = [
+// id, icon config, label, base cost, effect, effect per purchase
+{
+ id: 'tap',
+ icon: {
+ width: 100,
+ height: 100,
+ color: 0xffc300,
+ shape: 'ellipse'
+ },
+ label: "Bigger Tongs",
+ baseCost: 50,
+ effect: "coinsPerTap",
+ per: 1
+}, {
+ id: 'idle',
+ icon: {
+ width: 100,
+ height: 100,
+ color: 0x00b894,
+ shape: 'box'
+ },
+ label: "Auto Grill",
+ baseCost: 200,
+ effect: "coinsPerSec",
+ per: 1
+}];
+// --- Asset Initialization ---
+for (var i = 0; i < grillables.length; i++) {}
+for (var i = 0; i < upgrades.length; i++) {}
+// --- State ---
+var coins = storage.coins || 0;
+var coinsPerTap = storage.coinsPerTap || 1;
+var coinsPerSec = storage.coinsPerSec || 0;
+var unlockedItems = storage.unlockedItems || [0]; // indices of grillables
+var lastActive = storage.lastActive || Date.now();
+var currentItemIdx = unlockedItems[unlockedItems.length - 1]; // last unlocked
+var upgradeLevels = {
+ tap: Math.floor((coinsPerTap - 1) / upgrades[0].per),
+ idle: Math.floor(coinsPerSec / upgrades[1].per)
+};
+// --- GUI Elements ---
+var coinTxt = new Text2('', {
+ size: 120,
+ fill: 0xFFE066
+});
+coinTxt.anchor.set(0.5, 0);
+LK.gui.top.addChild(coinTxt);
+var grillNameTxt = new Text2('', {
+ size: 64,
+ fill: "#fff"
+});
+grillNameTxt.anchor.set(0.5, 0);
+grillNameTxt.y = 140;
+LK.gui.top.addChild(grillNameTxt);
+// --- Grill Item ---
+var grillItem = new GrillItem();
+grillItem.assetId = grillables[currentItemIdx].id;
+grillItem.x = 2048 / 2;
+grillItem.y = 1200;
+game.addChild(grillItem);
+// --- Upgrades ---
+var upgradeBtns = [];
+for (var i = 0; i < upgrades.length; i++) {
+ var upg = upgrades[i];
+ var btn = new UpgradeButton();
+ btn.iconId = 'upgrade_' + upg.id;
+ btn.labelText = upg.label;
+ btn.x = 2048 / 2 + (i === 0 ? -260 : 260);
+ btn.y = 2200;
+ btn.setCost(getUpgradeCost(upg.id));
+ game.addChild(btn);
+ upgradeBtns.push(btn);
+}
+// --- Unlockable Items Buttons ---
+var unlockBtns = [];
+for (var i = 1; i < grillables.length; i++) {
+ var g = grillables[i];
+ var btn = new Container();
+ var bg = btn.attachAsset('upgradeBtnBg', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ var icon = btn.attachAsset(g.id, {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 0.5,
+ scaleY: 0.5
+ });
+ var costTxt = new Text2(g.unlock + "🪙", {
+ size: 48,
+ fill: 0xFFE066
+ });
+ costTxt.anchor.set(0.5, 0);
+ costTxt.y = 80;
+ btn.addChild(costTxt);
+ btn.x = 2048 / 2 + (i - 2) * 220;
+ btn.y = 1700;
+ btn.grillIdx = i;
+ game.addChild(btn);
+ unlockBtns.push(btn);
+}
+// --- Helper Functions ---
+function getUpgradeCost(id) {
+ if (id === 'tap') {
+ return upgrades[0].baseCost * Math.pow(2, upgradeLevels.tap);
+ } else if (id === 'idle') {
+ return upgrades[1].baseCost * Math.pow(2, upgradeLevels.idle);
+ }
+ return 9999;
+}
+function updateCoinDisplay() {
+ coinTxt.setText(coins + "🪙");
+}
+function updateGrillName() {
+ grillNameTxt.setText(grillables[currentItemIdx].name);
+}
+function updateUpgradeButtons() {
+ for (var i = 0; i < upgradeBtns.length; i++) {
+ var upg = upgrades[i];
+ upgradeBtns[i].setCost(getUpgradeCost(upg.id));
+ }
+}
+function updateUnlockBtns() {
+ for (var i = 0; i < unlockBtns.length; i++) {
+ var idx = unlockBtns[i].grillIdx;
+ if (unlockedItems.indexOf(idx) !== -1) {
+ unlockBtns[i].alpha = 0.3;
+ } else {
+ unlockBtns[i].alpha = 1;
+ }
+ }
+}
+// --- Idle Coin Calculation (on load) ---
+var now = Date.now();
+if (coinsPerSec > 0 && lastActive && now > lastActive) {
+ var secondsAway = Math.floor((now - lastActive) / 1000);
+ var idleCoins = secondsAway * coinsPerSec;
+ if (idleCoins > 0) {
+ coins += idleCoins;
+ // Fun effect: flash coin text
+ LK.effects.flashObject(coinTxt, 0x83de44, 800);
+ }
+}
+lastActive = now;
+storage.lastActive = now;
+// --- Save State Helper ---
+function saveState() {
+ storage.coins = coins;
+ storage.coinsPerTap = coinsPerTap;
+ storage.coinsPerSec = coinsPerSec;
+ storage.unlockedItems = unlockedItems;
+ storage.lastActive = Date.now();
+}
+// --- Main Tap Handler (grill tap) ---
+grillItem.down = function (x, y, obj) {
+ coins += coinsPerTap;
+ updateCoinDisplay();
+ grillItem.sizzle();
+ saveState();
+};
+// --- Upgrade Button Handlers ---
+for (var i = 0; i < upgradeBtns.length; i++) {
+ (function (btn, upg) {
+ btn.down = function (x, y, obj) {
+ var cost = getUpgradeCost(upg.id);
+ if (coins >= cost) {
+ coins -= cost;
+ if (upg.id === 'tap') {
+ coinsPerTap += upg.per;
+ upgradeLevels.tap++;
+ } else if (upg.id === 'idle') {
+ coinsPerSec += upg.per;
+ upgradeLevels.idle++;
+ }
+ btn.flash();
+ updateCoinDisplay();
+ updateUpgradeButtons();
+ saveState();
+ } else {
+ // Not enough coins: shake
+ tween(btn, {
+ x: btn.x - 20
+ }, {
+ duration: 60,
+ onFinish: function onFinish() {
+ tween(btn, {
+ x: btn.x + 40
+ }, {
+ duration: 80,
+ onFinish: function onFinish() {
+ tween(btn, {
+ x: btn.x - 20
+ }, {
+ duration: 60
+ });
+ }
+ });
+ }
+ });
+ }
+ };
+ })(upgradeBtns[i], upgrades[i]);
+}
+// --- Unlockable Grillable Handlers ---
+for (var i = 0; i < unlockBtns.length; i++) {
+ (function (btn, idx) {
+ btn.down = function (x, y, obj) {
+ if (unlockedItems.indexOf(idx) !== -1) {
+ // Already unlocked: switch to this grillable
+ currentItemIdx = idx;
+ grillItem.setAsset(grillables[idx].id);
+ updateGrillName();
+ saveState();
+ return;
+ }
+ var cost = grillables[idx].unlock;
+ if (coins >= cost) {
+ coins -= cost;
+ unlockedItems.push(idx);
+ currentItemIdx = idx;
+ grillItem.setAsset(grillables[idx].id);
+ updateCoinDisplay();
+ updateUnlockBtns();
+ updateGrillName();
+ LK.effects.flashObject(btn, 0x83de44, 400);
+ saveState();
+ } else {
+ // Not enough coins: shake
+ tween(btn, {
+ x: btn.x - 20
+ }, {
+ duration: 60,
+ onFinish: function onFinish() {
+ tween(btn, {
+ x: btn.x + 40
+ }, {
+ duration: 80,
+ onFinish: function onFinish() {
+ tween(btn, {
+ x: btn.x - 20
+ }, {
+ duration: 60
+ });
+ }
+ });
+ }
+ });
+ }
+ };
+ })(unlockBtns[i], unlockBtns[i].grillIdx);
+}
+// --- Update GUI on load ---
+updateCoinDisplay();
+updateUpgradeButtons();
+updateUnlockBtns();
+updateGrillName();
+// --- Idle Coin Timer ---
+var idleTimer = LK.setInterval(function () {
+ if (coinsPerSec > 0) {
+ coins += coinsPerSec;
+ updateCoinDisplay();
+ saveState();
+ }
+}, 1000);
+// --- Save last active time on pause/leave ---
+LK.on('pause', function () {
+ storage.lastActive = Date.now();
+});
+// --- Game Update (not much needed) ---
+game.update = function () {
+ // No per-frame logic needed for MVP
+};
\ No newline at end of file