diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 623d73a..0000000 --- a/.editorconfig +++ /dev/null @@ -1,7 +0,0 @@ -root = true -[*] -end_of_line = lf -insert_final_newline = true -[*.go] -indent_style = tab -indent_size = 4 diff --git a/.gitignore b/.gitignore index d46f6d4..1dce0e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ /tetris -/tetris.exe -/config.json diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index c009c82..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,211 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines. - ---- -## [1.2.0](http://git.sargeras.net/pnx/tetris-go/src/1.2.0) - 2026-04-04 - -### Features - -- **(menu)** use list.SetSelected() on Enter() so that no audio is played. ([ef79889](http://git.sargeras.net/pnx/tetris-go/commit/ef798896c3493d67b823ae762bb0c8a14c6a2f45)) -- **(menu)** play "menu enter" sound when button action is triggered ([eb7486f](http://git.sargeras.net/pnx/tetris-go/commit/eb7486f57cf9878cd626fcbf39417b714e9acaef)) -- **(music)** add gameplay background music ([562d47a](http://git.sargeras.net/pnx/tetris-go/commit/562d47a72366eeaca88a0eeb3c3ee5f75bed36f8)) -- **(ui)** add ListBox.SetSelected() ([f3d7995](http://git.sargeras.net/pnx/tetris-go/commit/f3d7995bc25b8da9055f6a7ead404c3ad21df852)) - ---- -## [1.1.0](http://git.sargeras.net/pnx/tetris-go/src/1.1.0) - 2025-10-28 - -### Features - -- **(audio)** add SetVolume() and Volume() ([678670f](http://git.sargeras.net/pnx/tetris-go/commit/678670fd376d2098f4ad5ef1491f716545f907c1)) -- **(engine)** add number clamping functions ([7d5d5b9](http://git.sargeras.net/pnx/tetris-go/commit/7d5d5b9a54b92e00fa159bef64cc8e096c626726)) -- **(input)** add KeyPressedWithRepeat() ([3d3e8d7](http://git.sargeras.net/pnx/tetris-go/commit/3d3e8d7a445e3f33902006b194b1aa7712009bbe)) -- **(render)** adding DrawRectOutlineBorder() ([d407eaa](http://git.sargeras.net/pnx/tetris-go/commit/d407eaad8b3f68b2ae1ab6a068b6ca576f0d83b2)) -- **(ui)** menu should not call audio module directly, make it more modular with and onSelect event callback ([a175654](http://git.sargeras.net/pnx/tetris-go/commit/a1756549f9dbefbfcda259f6839dab6159fd5b2a)) -- **(ui)** call onSelect in Select() function and make Next()/Previous() use that function ([0bc5863](http://git.sargeras.net/pnx/tetris-go/commit/0bc5863284bc09bf43c9a3782d4826c576e7fa9b)) -- **(ui)** implement new ui system ([6cf0e4a](http://git.sargeras.net/pnx/tetris-go/commit/6cf0e4abb4ae67ac5ad1f1ec833caf2abd73b4c5)) -- **(ui)** add options menu ([30b1946](http://git.sargeras.net/pnx/tetris-go/commit/30b194619ff76136bb3f5ec68127c8c4bac445a5)) -- add line clear animation ([db9ec53](http://git.sargeras.net/pnx/tetris-go/commit/db9ec53d354d746748aba10be37bed53d79ecd4b)) -- dont close application when user presses Esc key ([d1ed556](http://git.sargeras.net/pnx/tetris-go/commit/d1ed55677e2750867150ec4911b6b79bab1fb401)) -- implement persistant configuration ([8a02249](http://git.sargeras.net/pnx/tetris-go/commit/8a0224984572b8c167d5298a35823b6b2daaabe5)) - -### Miscellaneous Chores - -- rename Rows to Lines in Grid as Lines are more common in tetris. ([190c6ad](http://git.sargeras.net/pnx/tetris-go/commit/190c6ad914430edbbedfa29612b6700071e9ab1e)) -- add trimpath and ldflags to build command ([e5c86a1](http://git.sargeras.net/pnx/tetris-go/commit/e5c86a1528100e7f3f4018bfc2739ba95eb9472e)) -- setup git-cliff ([fd15ed0](http://git.sargeras.net/pnx/tetris-go/commit/fd15ed0b11b6db29ebf84e3ed5f20790abef334a)) - -### Refactoring - -- move ui code from game/state/handlers/menu.go to its own package in game/ui ([bcd6025](http://git.sargeras.net/pnx/tetris-go/commit/bcd6025aa371904d8d56069ce936d72142d10082)) - ---- -## [1.0.0](http://git.sargeras.net/pnx/tetris-go/src/1.0.0) - 2025-10-28 - -### Bug Fixes - -- **(audio)** implement IsPlaying() correctly ([1b187d7](http://git.sargeras.net/pnx/tetris-go/commit/1b187d74120b15b1eb2c044bada468ff293e5319)) - -### Features - -- **(assets)** add icon ([5b54466](http://git.sargeras.net/pnx/tetris-go/commit/5b54466018962e8ab02c8f34893bb66c03769910)) -- **(assets)** adding logo data ([dc83316](http://git.sargeras.net/pnx/tetris-go/commit/dc833168865024d01092a240be02b3bbddfbd129)) -- **(engine)** add LoadImageFromMemory() ([93736e0](http://git.sargeras.net/pnx/tetris-go/commit/93736e060ef02b0c26cdcaf22f104b3e62e17507)) -- **(gameover)** only allow the user to switch state after the gameover sound has been played. ([53ac384](http://git.sargeras.net/pnx/tetris-go/commit/53ac3840baf207b5c983535a3fbf69a11f4b7a52)) -- **(gameover)** make gameover state abit more interesting. ([4319ec5](http://git.sargeras.net/pnx/tetris-go/commit/4319ec55e5b3b101aea56972b91fec3e8d9ee69f)) -- **(menu)** draw logo ([9b11f3b](http://git.sargeras.net/pnx/tetris-go/commit/9b11f3b2200983d36dff0e0d5ef18619e608619d)) -- **(menu)** put entries rendering into its own function ([ca481ad](http://git.sargeras.net/pnx/tetris-go/commit/ca481ad7c7ae0be652d12552128d0724351e2d6c)) -- set window icon ([57e57f3](http://git.sargeras.net/pnx/tetris-go/commit/57e57f3037ac026ec24c264617f648a234c59862)) - -### Miscellaneous Chores - -- **(makefile)** add .PHONY target again ([634fd7c](http://git.sargeras.net/pnx/tetris-go/commit/634fd7ce408cf4c8fd36db9bd974da95269e3080)) -- fix build for windows ([213b9a0](http://git.sargeras.net/pnx/tetris-go/commit/213b9a0d9eb4d0871566d549ece09128b97976bc)) -- add editorconfig ([cb6e66b](http://git.sargeras.net/pnx/tetris-go/commit/cb6e66bc45fe35cf209f780700cd424108fa759c)) -- update readme and add license ([02a915b](http://git.sargeras.net/pnx/tetris-go/commit/02a915b8373f9ace1ae9702b565f82065b4289ed)) - ---- -## [0.13.0](http://git.sargeras.net/pnx/tetris-go/src/v0.13.0) - 2025-09-24 - -### Bug Fixes - -- reset state on Enter() ([cd750a5](http://git.sargeras.net/pnx/tetris-go/commit/cd750a55083f056ccc06b67d7193288c9ec2b291)) - -### Features - -- **(assets)** add menu sound effects ([7c02075](http://git.sargeras.net/pnx/tetris-go/commit/7c020750755fbf016c48ba7c6607ecde1da2c444)) -- **(assets)** add gameover sound ([9f055c6](http://git.sargeras.net/pnx/tetris-go/commit/9f055c6bb82d583c36289cf510f7200d0382a6a3)) -- **(fsm)** make the special state "quit" exit the fsm. ([35e73f2](http://git.sargeras.net/pnx/tetris-go/commit/35e73f2b37e75c0af270c11f04b28de27e08f383)) -- **(gameover)** play gameover sound ([dcad4ad](http://git.sargeras.net/pnx/tetris-go/commit/dcad4ad0a5f973a7bf95f63277c591c79956ac68)) -- **(gameover)** center text ([8f8be3d](http://git.sargeras.net/pnx/tetris-go/commit/8f8be3d6f7906a84b077e71082ba3bcb73180c51)) -- **(menu)** play sound effects. ([68b67e4](http://git.sargeras.net/pnx/tetris-go/commit/68b67e4585a4f38974b5246089b675f92d40d96b)) -- **(render)** add DrawTextCenter() ([346cf35](http://git.sargeras.net/pnx/tetris-go/commit/346cf350d8375df56ca2fbf6044d7600d03ccdf1)) -- add basic state machine implementation ([75b4981](http://git.sargeras.net/pnx/tetris-go/commit/75b498156667fb7569d3e08dbb00a9b60e69c679)) -- refactor gameplay logic from main.go to a state machine handler. ([29225b7](http://git.sargeras.net/pnx/tetris-go/commit/29225b7007cd154243c48bad8c321b6c4633f323)) -- add basic menu state ([53a31ea](http://git.sargeras.net/pnx/tetris-go/commit/53a31ea8ade6a63ecf3c56c3b061f4715859e18f)) -- add basic game over state ([6d15a80](http://git.sargeras.net/pnx/tetris-go/commit/6d15a806f6172b674617511ab57fc258e55e2a77)) -- register menu and gameover states. ([b8397ae](http://git.sargeras.net/pnx/tetris-go/commit/b8397aefdb0e653266b3851f81708dea28ac7063)) -- start the machine in the menu state ([cae34a5](http://git.sargeras.net/pnx/tetris-go/commit/cae34a52aa3c4d78607065feb976b98e53a5475c)) -- detect game over condition and switch state. ([54a7821](http://git.sargeras.net/pnx/tetris-go/commit/54a7821df0016eb0f1deeeb7c8a55af121d3e76a)) -- improve menu ([288e710](http://git.sargeras.net/pnx/tetris-go/commit/288e710b9f298de25d0a16564287c54e788fb551)) - ---- -## [0.12.0](http://git.sargeras.net/pnx/tetris-go/src/v0.12.0) - 2025-09-21 - -### Features - -- **(assets)** add sound files ([1799422](http://git.sargeras.net/pnx/tetris-go/commit/17994225917cfe0f5085b1f968deec55273482b2)) -- **(assets)** add sound library ([1c31fd4](http://git.sargeras.net/pnx/tetris-go/commit/1c31fd43ac99239db184d3144383efb03e59ad90)) -- engine audio code ([5860aa6](http://git.sargeras.net/pnx/tetris-go/commit/5860aa69d6cea02e321d6de768adfddc39cc3b52)) -- initialize and use audio subsystem. ([173adad](http://git.sargeras.net/pnx/tetris-go/commit/173adadb8405804e641f5bd56e666d2b0a1a3861)) -- add sound on shape locked and row clear ([a5c9e68](http://git.sargeras.net/pnx/tetris-go/commit/a5c9e687ee604a5c783c7873ffa0d5a5e5f6813c)) - ---- -## [0.11.0](http://git.sargeras.net/pnx/tetris-go/src/v0.11.0) - 2025-09-17 - -### Features - -- add game/shape_queue.go ([5c45032](http://git.sargeras.net/pnx/tetris-go/commit/5c4503268f62c30d814db74dde56bfdbf5aa9293)) -- use ShapeQueue to implement shape RNG ([d6c9389](http://git.sargeras.net/pnx/tetris-go/commit/d6c9389e60252048c80f44572b024607d19c8814)) - ---- -## [0.10.0](http://git.sargeras.net/pnx/tetris-go/src/v0.10.0) - 2025-09-17 - -### Features - -- draw next shape ([12712e0](http://git.sargeras.net/pnx/tetris-go/commit/12712e0172071013f323d87c75d573645d7f2195)) - ---- -## [0.9.0](http://git.sargeras.net/pnx/tetris-go/src/v0.9.0) - 2025-09-15 - -### Features - -- add score type ([14520e0](http://git.sargeras.net/pnx/tetris-go/commit/14520e047c2fdff69a5489ba1dbfd565f09826d8)) -- show score and increment when lines are cleared ([b6a33d0](http://git.sargeras.net/pnx/tetris-go/commit/b6a33d017f718659a5b21f2d80b8c706d4ee28be)) - ---- -## [0.8.0](http://git.sargeras.net/pnx/tetris-go/src/v0.8.0) - 2025-09-15 - -### Features - -- **(grid)** add IsRowFull() ([eb31bdf](http://git.sargeras.net/pnx/tetris-go/commit/eb31bdf50cfb30f2b3ee4e3f3771b938330735f7)) -- **(grid)** add ClearRow() ([8903319](http://git.sargeras.net/pnx/tetris-go/commit/890331991ffd12c0bc41ba9ba329fcc661e3feef)) -- **(grid)** add MoveRowDown() ([1d7423d](http://git.sargeras.net/pnx/tetris-go/commit/1d7423d8867a802f442b18143824a4cffa8fb9d0)) -- **(grid)** add ClearFullRows() ([2a7f394](http://git.sargeras.net/pnx/tetris-go/commit/2a7f394b7f129b8a8d3a44ea314ba7c654688abc)) -- clear full lines after locking shape ([ac4d911](http://git.sargeras.net/pnx/tetris-go/commit/ac4d911efd0bfa7aeff57acf8535c916dc6a22ca)) - ---- -## [0.7.0](http://git.sargeras.net/pnx/tetris-go/src/v0.7.0) - 2025-09-15 - -### Features - -- soft drop ([8f11a99](http://git.sargeras.net/pnx/tetris-go/commit/8f11a99e08edc0473458f28adb4db87a75092a04)) - ---- -## [0.6.0](http://git.sargeras.net/pnx/tetris-go/src/v0.6.0) - 2025-09-15 - -### Features - -- **(shape)** add RotateCW() and RotateCCW() ([6131b75](http://git.sargeras.net/pnx/tetris-go/commit/6131b751e568c7a48500428b639457d2cd426eb3)) -- rotate shape when UP is pressed. ([4ce5b82](http://git.sargeras.net/pnx/tetris-go/commit/4ce5b82b25acd93ce7cfaab18b300b6c25f2ba30)) - ---- -## [0.5.0](http://git.sargeras.net/pnx/tetris-go/src/v0.5.0) - 2025-09-15 - -### Features - -- **(collision)** check grid bounds in x axis. ([aeaf6cb](http://git.sargeras.net/pnx/tetris-go/commit/aeaf6cb5c47027c07958f7a7c2c07decb1ed49e7)) -- add shape movement ([ac14dfe](http://git.sargeras.net/pnx/tetris-go/commit/ac14dfe8b3b2a60dcb3bf71bbf89a0b42c99e389)) - -### Miscellaneous Chores - -- update go.mod ([d6a9e80](http://git.sargeras.net/pnx/tetris-go/commit/d6a9e80fbb79532a2f63d88b126b2fd448de3c7f)) - ---- -## [0.4.0](http://git.sargeras.net/pnx/tetris-go/src/v0.4.0) - 2025-09-14 - -### Bug Fixes - -- check bound before placing a block in LockShape() ([ca6497d](http://git.sargeras.net/pnx/tetris-go/commit/ca6497d36223508a717177bc8f21d8238aa6eae5)) - -### Features - -- **(collision)** check for collision against blocks in the grid. ([dab2570](http://git.sargeras.net/pnx/tetris-go/commit/dab2570b76c224ad3b6b3741c3cb0381d403fa79)) - ---- -## [0.3.0](http://git.sargeras.net/pnx/tetris-go/src/v0.3.0) - 2025-09-14 - -### Features - -- lock and spawn shapes ([a6072e5](http://git.sargeras.net/pnx/tetris-go/commit/a6072e5b05a265bfd38c3b784731bfaed165e2d7)) - -### Refactoring - -- cleanup main loop code by moving related code into update/render functions respectively. ([3c84709](http://git.sargeras.net/pnx/tetris-go/commit/3c847094a060caec81dd805bc58f2ab1b93b2289)) - ---- -## [0.2.0](http://git.sargeras.net/pnx/tetris-go/src/v0.2.0) - 2025-09-14 - -### Features - -- **(draw)** add grid.DrawBlock() ([4dd6d93](http://git.sargeras.net/pnx/tetris-go/commit/4dd6d936535e9e39d09b365d9ae005c4a9ddefb5)) -- **(draw)** add DrawShape() ([02157a1](http://git.sargeras.net/pnx/tetris-go/commit/02157a12eadc96a6be8e6212c7fe8357a290c432)) -- add engine/core/vec2.go ([9387402](http://git.sargeras.net/pnx/tetris-go/commit/93874028b07c879a718d9dc41a0c98a17fd55bbc)) -- add shape struct ([6564879](http://git.sargeras.net/pnx/tetris-go/commit/656487903e282507c0bfabac5657c124a63c4d7f)) -- draw shape on grid. ([63a3662](http://git.sargeras.net/pnx/tetris-go/commit/63a3662dbe6f6a801fdbcbc07e99f24f1f0afbb6)) -- add engine/core/interval_timer.go ([ffbf133](http://git.sargeras.net/pnx/tetris-go/commit/ffbf1332c5c0f1035a58c9b1d98c12c16c76f66b)) -- implement shape drop ([ab69d50](http://git.sargeras.net/pnx/tetris-go/commit/ab69d501b9e0e82358873e431cc934ad74821361)) -- add CheckShapeCollision() ([27c10af](http://git.sargeras.net/pnx/tetris-go/commit/27c10af4248bfd3150850006ecdbd6833c7c27c9)) -- add collision check on shape drop. ([50a7666](http://git.sargeras.net/pnx/tetris-go/commit/50a76662469874ea48b32e7dc03fcbf75977e4a6)) - ---- -## [0.1.0](http://git.sargeras.net/pnx/tetris-go/src/0.1.0) - 2025-09-14 - -### Features - -- **(assets)** add constant with the size of blocks in the sprite texture. ([93a660e](http://git.sargeras.net/pnx/tetris-go/commit/93a660e8d3f2fe7fa06d4066b2a74bc84347978b)) -- add game/block.go ([6e21b1f](http://git.sargeras.net/pnx/tetris-go/commit/6e21b1fcb2cd0f6554e5af3b52222f31c461915c)) -- add game/grid.go ([2a84c7b](http://git.sargeras.net/pnx/tetris-go/commit/2a84c7bf6a8a4faee67c5b813faca587621aa6aa)) -- make rendering work with the grid struct. ([1b9ba08](http://git.sargeras.net/pnx/tetris-go/commit/1b9ba081e2bf0b7f4be0088ea6d7d6c42294b3ad)) - - diff --git a/LICENSE b/LICENSE deleted file mode 100644 index b69871e..0000000 --- a/LICENSE +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/Makefile b/Makefile index 87a9e30..7017b46 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,6 @@ GO = go -GOFLAGS = -trimpath -ldflags="-s -w" .PHONY: tetris tetris : - $(GO) build $(GOFLAGS) tetris.go - -release : NEXT_VER = $(shell git-cliff --bumped-version) -release : - git-cliff --bump -o CHANGELOG.md - git add CHANGELOG.md - git commit -m "chore(version): Bump to $(NEXT_VER)" - git tag $(NEXT_VER) + $(GO) build -o $@ diff --git a/README.md b/README.md index a1ff326..a9138a5 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,23 @@ -# Tetris (Go + Raylib) - -A small, self‑contained Tetris clone written in Go using raylib‑go. -It features classic gameplay (movement, rotation, line clearing, scoring), -a simple menu and game‑over flow, pixel‑art tiles, and embedded audio — all shipped as a single binary via `go:embed`. - -## Features - -- Classic Tetris gameplay with 7 tetrominoes -- Next piece preview and line clearing with scoring -- Keyboard controls for movement, rotation, and soft drop -- Simple menu and game‑over screens via a tiny state machine -- Embedded sprites, icon, SFX, and background music (no external files at runtime) - -## Controls - -- Left / Right: Move piece -- Up: Rotate clockwise -- Down (hold): Soft drop (faster fall) -- Enter: Select in menu, restart from game over -- Q: Return to menu from game over - -## Scoring - -Points are awarded per simultaneous line clear using: `score = L^2 * 100` -where `L` is the number of lines cleared at once. - -- 1 line → 100 -- 2 lines → 400 -- 3 lines → 900 -- 4 lines → 1600 - -## Build and Run - -Prerequisites: -- Go (as indicated in `go.mod`) -- A working C toolchain (required by raylib‑go). On Linux you may need basic X11/ALSA dev packages. - -### Build - -Using `make` - -```sh -make -``` - -Using go directly: - -```sh -go build tetris.go -``` -### Run - -```sh -./tetris -``` - -## Project Structure - -- `tetris.go`: Program entrypoint; initializes window, renderer, audio, and the state machine. -- `assets/`: Embedded resources (sprites, icon, SFX, music) and a compact tile‑font mapping. -- `engine/`: Lightweight helpers for audio, graphics, font/tile rendering, timing, and render context. -- `game/`: Core gameplay (grid, shapes, collision, scoring) and state handlers for `menu`, `gameplay`, and `gameover`. +# Tetris in golang ## Roadmap -- [x] Line clear animations -- [ ] Hard drop and ghost piece -- [ ] Level/speed progression and pause -- [ ] High score persistence -- [ ] Custom key bindings +- [x] Initial Commit - Project Scaffolding (rendering engine) +- [x] 0.1 - Grid +- [x] 0.2 - Basic shapes (falling + collision with floor) +- [x] 0.3 - Shapes: Lock + spawn +- [x] 0.4 - Collision with with blocks in grid +- [x] 0.5 - left/right movement +- [x] 0.6 - Shapes rotation +- [ ] 0.7 - Soft Drop +- [ ] 0.8 - Clear lines +- [ ] 0.9 - Score +- [ ] 0.10 - Next shape +- [ ] 0.11 - RNG Shape generation +- [ ] 0.12 - Sound +- [ ] 0.13 - State machine: Menu, Gameplay and Game Over screen. +- [ ] 1.0 - Bugfixes + +## Future things (aka Rad stuffs!) + +- [ ] Animations (Clear lines) diff --git a/assets/background.mp3 b/assets/background.mp3 deleted file mode 100644 index 58a10a4..0000000 Binary files a/assets/background.mp3 and /dev/null differ diff --git a/assets/blip.mp3 b/assets/blip.mp3 deleted file mode 100644 index 500c364..0000000 Binary files a/assets/blip.mp3 and /dev/null differ diff --git a/assets/blip2.mp3 b/assets/blip2.mp3 deleted file mode 100644 index 971ebb4..0000000 Binary files a/assets/blip2.mp3 and /dev/null differ diff --git a/assets/def.go b/assets/def.go index 85a95e8..8c6691a 100644 --- a/assets/def.go +++ b/assets/def.go @@ -6,9 +6,6 @@ import ( "tetris/engine/graphics/font" ) -//go:embed icon.png -var Icon []byte - //go:embed sprites.png var Sprite []byte @@ -58,17 +55,3 @@ var Font = font.TileFont{ }, CaseInsensitive: true, } - -// Sounds - -//go:embed blip.mp3 -var SFXBlipData []byte - -//go:embed blip2.mp3 -var SFXBlip2Data []byte - -//go:embed gameover.mp3 -var SFXGameOverData []byte - -//go:embed background.mp3 -var SNDBackgroundData []byte diff --git a/assets/gameover.mp3 b/assets/gameover.mp3 deleted file mode 100644 index 3222d6b..0000000 Binary files a/assets/gameover.mp3 and /dev/null differ diff --git a/assets/icon.png b/assets/icon.png deleted file mode 100644 index 3e6916f..0000000 Binary files a/assets/icon.png and /dev/null differ diff --git a/assets/logo.go b/assets/logo.go deleted file mode 100644 index 7305ef4..0000000 --- a/assets/logo.go +++ /dev/null @@ -1,14 +0,0 @@ -package assets - -const ( - LOGO_STRIDE = 20 - LOGO_HEIGHT = 5 -) - -var Logo = [LOGO_HEIGHT * LOGO_STRIDE]byte{ - 1, 1, 1, 0, 2, 2, 0, 3, 3, 3, 0, 4, 4, 0, 0, 5, 0, 6, 6, 6, - 0, 1, 0, 0, 2, 0, 0, 0, 3, 0, 0, 4, 0, 4, 0, 5, 0, 6, 0, 0, - 0, 1, 0, 0, 2, 2, 0, 0, 3, 0, 0, 4, 4, 0, 0, 5, 0, 0, 6, 0, - 0, 1, 0, 0, 2, 0, 0, 0, 3, 0, 0, 4, 0, 4, 0, 5, 0, 0, 0, 6, - 0, 1, 0, 0, 2, 2, 0, 0, 3, 0, 0, 4, 0, 4, 0, 5, 0, 6, 6, 6, -} diff --git a/assets/sound.go b/assets/sound.go deleted file mode 100644 index bd12a39..0000000 --- a/assets/sound.go +++ /dev/null @@ -1,24 +0,0 @@ -package assets - -import ( - "tetris/engine/audio" -) - -const ( - SFX_SHAPE_LOCKED audio.SoundID = 0 - SFX_ROW_CLEARED audio.SoundID = 1 - SFX_MUSIC audio.SoundID = 3 - SFX_MENU_SELECT audio.SoundID = 0 - SFX_MENU_ENTER audio.SoundID = 1 - SFX_MENU_SOUND_VOLUME_SELECT audio.SoundID = 1 - SFX_GAME_OVER audio.SoundID = 2 -) - -func LoadSound() *audio.Library { - return &audio.Library{ - audio.LoadFromMemory(".mp3", SFXBlipData), - audio.LoadFromMemory(".mp3", SFXBlip2Data), - audio.LoadFromMemory(".mp3", SFXGameOverData), - audio.LoadFromMemory(".mp3", SNDBackgroundData), - } -} diff --git a/cliff.toml b/cliff.toml deleted file mode 100644 index 3153cb1..0000000 --- a/cliff.toml +++ /dev/null @@ -1,86 +0,0 @@ -# git-cliff ~ configuration file -# https://git-cliff.org/docs/configuration - -[changelog] -# A Tera template to be rendered as the changelog's footer. -# See https://keats.github.io/tera/docs/#introduction -header = """ -# Changelog\n -All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.\n -""" -# A Tera template to be rendered for each release in the changelog. -# See https://keats.github.io/tera/docs/#introduction -body = """ ---- -{% if version %}\ - ## [{{ version | trim_start_matches(pat="v") }}]($REPO/src/{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }} -{% else %}\ - ## [unreleased] -{% endif %}\ -{% for group, commits in commits | group_by(attribute="group") %} - ### {{ group | striptags | trim | upper_first }} - {% for commit in commits - | filter(attribute="scope") - | sort(attribute="scope") %} - - **({{commit.scope}})**{% if commit.breaking %} [**breaking**]{% endif %} \ - {{ commit.message }} ([{{ commit.id | truncate(length=7, end="") }}]($REPO/commit/{{ commit.id }})) - {%- endfor -%} - {% raw %}\n{% endraw %}\ - {%- for commit in commits %} - {%- if commit.scope -%} - {% else -%} - - {% if commit.breaking %} [**breaking**]{% endif %}\ - {{ commit.message }} ([{{ commit.id | truncate(length=7, end="") }}]($REPO/commit/{{ commit.id }})) - {% endif -%} - {% endfor -%} -{% endfor %}\n -""" -# A Tera template to be rendered as the changelog's footer. -# See https://keats.github.io/tera/docs/#introduction -footer = """ - -""" -# Remove leading and trailing whitespaces from the changelog's body. -trim = true -# postprocessors -postprocessors = [ - # Replace the placeholder `` with a URL. - { pattern = '\$REPO', replace = "http://git.sargeras.net/pnx/tetris-go" }, -] - -[git] -# Parse commits according to the conventional commits specification. -# See https://www.conventionalcommits.org -conventional_commits = true -# Exclude commits that do not match the conventional commits specification. -filter_unconventional = true -# Split commits on newlines, treating each line as an individual commit. -split_commits = false -# An array of regex based parsers to modify commit messages prior to further processing. -commit_preprocessors = [ - # Replace issue numbers with link templates to be updated in `changelog.postprocessors`. - #{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/orhun/git-cliff/issues/${2}))"}, -] -# An array of regex based parsers for extracting data from the commit message. -# Assigns commits to groups. -# Optionally sets the commit's scope and can decide to exclude commits from further processing. -commit_parsers = [ - { message = "^feat", group = "Features" }, - { message = "^fix", group = "Bug Fixes" }, - { message = "^doc", group = "Documentation" }, - { message = "^perf", group = "Performance" }, - { message = "^refactor", group = "Refactoring" }, - { message = "^style", group = "Style" }, - { message = "^revert", group = "Revert" }, - { message = "^test", group = "Tests" }, - { message = "^chore\\(version\\):", skip = true }, - { message = "^chore", group = "Miscellaneous Chores" }, - { body = ".*security", group = "Security" }, -] -# Exclude commits that are not matched by any commit parser. -filter_commits = false -# Order releases topologically instead of chronologically. -topo_order = false -# Order of commits in each group/release within the changelog. -# Allowed values: newest, oldest -sort_commits = "oldest" diff --git a/engine/audio/default_manger.go b/engine/audio/default_manger.go deleted file mode 100644 index ae0a31a..0000000 --- a/engine/audio/default_manger.go +++ /dev/null @@ -1,43 +0,0 @@ -package audio - -var defaultManager = Manager{} - -func LoadLibrary(library *Library) { - defaultManager.Load(library) -} - -func SetVolume(value float32) { - defaultManager.SetVolume(value) -} - -func Volume() float32 { - return defaultManager.Volume() -} - -func Play(id SoundID) { - defaultManager.Play(id) -} - -func PlayLooped(id SoundID) { - defaultManager.PlayLooped(id) -} - -func Stop(id SoundID) { - defaultManager.Stop(id) -} - -func IsPlaying(id SoundID) bool { - return defaultManager.IsPlaying(id) -} - -func Pause() { - defaultManager.Pause() -} - -func Resume() { - defaultManager.Resume() -} - -func Update() { - defaultManager.Update() -} diff --git a/engine/audio/init.go b/engine/audio/init.go deleted file mode 100644 index fa3f4be..0000000 --- a/engine/audio/init.go +++ /dev/null @@ -1,11 +0,0 @@ -package audio - -import rl "github.com/gen2brain/raylib-go/raylib" - -func Init() { - rl.InitAudioDevice() -} - -func Exit() { - rl.CloseAudioDevice() -} diff --git a/engine/audio/library.go b/engine/audio/library.go deleted file mode 100644 index 0ed3759..0000000 --- a/engine/audio/library.go +++ /dev/null @@ -1,16 +0,0 @@ -package audio - -import rl "github.com/gen2brain/raylib-go/raylib" - -// Library is a map of SoundID's and sound data. -type Library []rl.Sound - -func (l Library) Unload() { - for _, stream := range l { - rl.UnloadSound(stream) - } -} - -func (l Library) Get(id SoundID) *rl.Sound { - return &l[id] -} diff --git a/engine/audio/load.go b/engine/audio/load.go deleted file mode 100644 index 97a56b4..0000000 --- a/engine/audio/load.go +++ /dev/null @@ -1,11 +0,0 @@ -package audio - -import ( - rl "github.com/gen2brain/raylib-go/raylib" -) - -func LoadFromMemory(typ string, data []byte) rl.Sound { - wave := rl.LoadWaveFromMemory(typ, data, int32(len(data))) - defer rl.UnloadWave(wave) - return rl.LoadSoundFromWave(wave) -} diff --git a/engine/audio/manager.go b/engine/audio/manager.go deleted file mode 100644 index 63300f6..0000000 --- a/engine/audio/manager.go +++ /dev/null @@ -1,103 +0,0 @@ -package audio - -import ( - "slices" - - rl "github.com/gen2brain/raylib-go/raylib" -) - -type audio struct { - id SoundID - snd rl.Sound -} - -type Manager struct { - library *Library - active []audio - paused bool -} - -func (sm *Manager) Load(library *Library) { - sm.library = library -} - -func (sm Manager) SetVolume(value float32) { - rl.SetMasterVolume(value) -} - -func (sm Manager) Volume() float32 { - return rl.GetMasterVolume() -} - -func (sm *Manager) play(id SoundID, looping bool) { - snd := sm.library.Get(id) - - alias := rl.LoadSoundAlias(*snd) - alias.Stream.Buffer.Looping = looping - - sm.active = append(sm.active, audio{ - id: id, - snd: alias, - }) - - rl.PlaySound(alias) -} - -func (sm *Manager) Play(id SoundID) { - sm.play(id, false) -} - -func (sm *Manager) PlayLooped(id SoundID) { - sm.play(id, true) -} - -func (sm *Manager) Stop(id SoundID) { - sm.active = slices.DeleteFunc(sm.active, func(a audio) bool { - if a.id == id { - rl.StopSound(a.snd) - rl.UnloadSoundAlias(a.snd) - return true - } - return false - }) -} - -func (sm Manager) IsPlaying(id SoundID) bool { - return slices.ContainsFunc(sm.active, func(e audio) bool { - return e.id == id - }) -} - -// Pause all active sounds -func (sm *Manager) Pause() { - sm.paused = true - for _, audio := range sm.active { - rl.PauseSound(audio.snd) - } -} - -// Resume all active sounds. -func (sm *Manager) Resume() { - for _, audio := range sm.active { - rl.ResumeSound(audio.snd) - } - sm.paused = false -} - -func (sm *Manager) Update() { - if sm.paused { - return - } - - active := sm.active[:0] - - for _, audio := range sm.active { - if !rl.IsSoundPlaying(audio.snd) { - rl.UnloadSoundAlias(audio.snd) - continue - } - active = append(active, audio) - } - - sm.active = active -} diff --git a/engine/audio/sound_id.go b/engine/audio/sound_id.go deleted file mode 100644 index a852919..0000000 --- a/engine/audio/sound_id.go +++ /dev/null @@ -1,4 +0,0 @@ -package audio - -type SoundID byte - diff --git a/engine/core/num_clamp.go b/engine/core/num_clamp.go deleted file mode 100644 index 5ebb6c6..0000000 --- a/engine/core/num_clamp.go +++ /dev/null @@ -1,9 +0,0 @@ -package core - -func ByteToClampedFloat32(v byte) float32 { - return min(float32(v)/255.0, 1.0) -} - -func ClampedFloat32ToByte(v float32) byte { - return byte(min(v, 1.0) * 255) -} diff --git a/engine/graphics/image.go b/engine/graphics/image.go deleted file mode 100644 index d92b93c..0000000 --- a/engine/graphics/image.go +++ /dev/null @@ -1,7 +0,0 @@ -package graphics - -import rl "github.com/gen2brain/raylib-go/raylib" - -func LoadImageFromMemory(typ string, data []byte) *rl.Image { - return rl.LoadImageFromMemory(typ, data, int32(len(data))) -} diff --git a/engine/input/keyboard.go b/engine/input/keyboard.go deleted file mode 100644 index 89134d5..0000000 --- a/engine/input/keyboard.go +++ /dev/null @@ -1,8 +0,0 @@ -package input - -import rl "github.com/gen2brain/raylib-go/raylib" - -func KeyPressedWithRepeat(key int32) bool { - return rl.IsKeyPressed(key) || rl.IsKeyPressedRepeat(key) -} - diff --git a/engine/render/rect.go b/engine/render/rect.go index 399c741..3ad993b 100644 --- a/engine/render/rect.go +++ b/engine/render/rect.go @@ -17,15 +17,10 @@ func DrawRectBorder(rect rl.RectangleInt32, col color.RGBA, border_size int32, b Height: float32(rect.Height), }, col) - DrawRectOutlineBorder(rect, border_size, border_col) -} - -// DrawRectOutlineBorder draws a border (outer) around the rectangle. -func DrawRectOutlineBorder(rect rl.RectangleInt32, size int32, col color.RGBA) { rl.DrawRectangleLinesEx(rl.Rectangle{ - X: float32(rect.X - size), - Y: float32(rect.Y - size), - Width: float32(rect.Width + (size * 2)), - Height: float32(rect.Height + (size * 2)), - }, float32(size), col) + X: float32(rect.X - border_size), + Y: float32(rect.Y - border_size), + Width: float32(rect.Width + (border_size * 2)), + Height: float32(rect.Height + (border_size * 2)), + }, float32(border_size), border_col) } diff --git a/engine/render/text.go b/engine/render/text.go index 419c96a..471f85e 100644 --- a/engine/render/text.go +++ b/engine/render/text.go @@ -29,9 +29,3 @@ func DrawText(x, y, size int32, text string, col color.RGBA) { destRect.X += float32(size) } } - -func DrawTextCenter(x, y, size int32, text string, col color.RGBA) { - l := int32(len(text)) - x = x - ((size * l) / 2) - DrawText(x, y, size, text, col) -} diff --git a/game/config.go b/game/config.go deleted file mode 100644 index 710fb0c..0000000 --- a/game/config.go +++ /dev/null @@ -1,21 +0,0 @@ -package game - -import ( - "io" - - "tetris/game/config" -) - -// Global config variable with default values. -var Config = &config.Config{ - SoundVolume: 255, -} - -func LoadConfig(configPath string) error { - cfg, err := config.LoadFromFile(configPath) - if err != nil && err != io.EOF { - return err - } - Config = cfg - return nil -} diff --git a/game/config/config.go b/game/config/config.go deleted file mode 100644 index 82ad3d8..0000000 --- a/game/config/config.go +++ /dev/null @@ -1,45 +0,0 @@ -package config - -import ( - "encoding/json" - "io" - "os" -) - -type Config struct { - fd *os.File - SoundVolume byte `json:"sound_volume"` -} - -func DefaultConfig(fd *os.File) *Config { - cfg := Config{ - SoundVolume: 255, - } - cfg.fd = fd - return &cfg -} - -func LoadFromFile(filename string) (*Config, error) { - fd, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0o644) - if err != nil { - return nil, err - } - - cfg := DefaultConfig(fd) - err = json.NewDecoder(fd).Decode(cfg) - return cfg, err -} - -func (c Config) Save() error { - if err := c.fd.Truncate(0); err != nil { - return err - } - if _, err := c.fd.Seek(0, io.SeekStart); err != nil { - return err - } - return json.NewEncoder(c.fd).Encode(c) -} - -func (c Config) Close() error { - return c.fd.Close() -} diff --git a/game/grid.go b/game/grid.go index d166cd0..43adb71 100644 --- a/game/grid.go +++ b/game/grid.go @@ -1,8 +1,6 @@ package game import ( - "slices" - "tetris/engine/graphics" ) @@ -32,60 +30,3 @@ func (g Grid) Tile(x, y byte) graphics.Tile { func (g *Grid) Set(x, y byte, c Block) { (*g)[uint16(x)+(uint16(y)*GRID_WIDTH)] = c } - -func (g *Grid) ClearFullLines() byte { - completed := byte(0) - for y := int(g.Height() - 1); y >= 0; y-- { - if g.IsLineFull(byte(y)) { - completed++ - g.ClearLine(byte(y)) - } else if completed > 0 { - g.MoveLineDown(byte(y), completed) - } - } - return completed -} - -func (g *Grid) FullLines() []byte { - lines := []byte{} - for y := byte(0); y < byte(g.Height()); y++ { - if g.IsLineFull(y) { - lines = append(lines, y) - } - } - return lines -} - -func (g *Grid) IsLineFull(y byte) bool { - for x := range byte(g.Width()) { - if g.At(x, y) == BLOCK_EMPTY { - return false - } - } - return true -} - -func (g *Grid) MoveLinesDown(rows ...byte) { - completed := byte(0) - for y := int(g.Height() - 1); y >= 0; y-- { - if slices.Contains(rows, byte(y)) { - completed++ - } else if completed > 0 { - g.MoveLineDown(byte(y), completed) - } - } -} - -func (g *Grid) MoveLineDown(y, num_lines byte) { - w := uint16(g.Width()) - src := uint16(y) * w - dst := uint16(y+num_lines) * w - copy(g[dst:dst+w], g[src:src+w]) - clear(g[src : src+w]) -} - -func (g *Grid) ClearLine(y byte) { - w := uint16(g.Width()) - n := uint16(y) * w - clear(g[n : n+w]) -} diff --git a/game/line_clear_animation.go b/game/line_clear_animation.go deleted file mode 100644 index 5c650d5..0000000 --- a/game/line_clear_animation.go +++ /dev/null @@ -1,57 +0,0 @@ -package game - -// LineClearAnimation clears completed lines cell-by-cell to create -// a visual wipe effect from the center outward. -// -// Once the lines are cleared, the remaining lines are moved down. -type LineClearAnimation struct { - // lines to clear. - lines []byte - // next index to clear to the left - leftIndex int8 - // next index to clear to the right - rightIndex int8 -} - -func (lca *LineClearAnimation) Reset() { - lca.lines = []byte{} -} - -func (lca LineClearAnimation) NumLines() int { - return len(lca.lines) -} - -func (lca *LineClearAnimation) SetLines(rows []byte) { - lca.lines = rows - - lca.leftIndex = (GRID_WIDTH - 1) / 2 - if (GRID_WIDTH-1)%2 != 0 { - lca.rightIndex = lca.leftIndex + 1 - } else { - lca.rightIndex = lca.leftIndex - } -} - -func (lca *LineClearAnimation) Update(grid *Grid) { - if lca.Completed() { - return - } - - if lca.leftIndex < 0 || lca.rightIndex > GRID_WIDTH { - grid.MoveLinesDown(lca.lines...) - lca.Reset() - return - } - - for _, lineIndex := range lca.lines { - grid.Set(byte(lca.leftIndex), lineIndex, BLOCK_EMPTY) - grid.Set(byte(lca.rightIndex), lineIndex, BLOCK_EMPTY) - } - - lca.leftIndex-- - lca.rightIndex++ -} - -func (lca LineClearAnimation) Completed() bool { - return lca.NumLines() == 0 -} diff --git a/game/score.go b/game/score.go deleted file mode 100644 index ac00af7..0000000 --- a/game/score.go +++ /dev/null @@ -1,33 +0,0 @@ -package game - -// SCORE_MAX is the highest allowed score (7 digits, 9,999,999). -// Any additions beyond this value are clamped. -const SCORE_MAX Score = 9999999 - -// Score is a running total of points earned. -// It’s a distinct type (over uint32) to prevent mixing raw integers with scores. -type Score uint32 - -// Lines adds points for clearing a number of lines at once. -// -// The formula used is: s = L^2 * 100 (where L is the number of lines cleared) -// That yields the following values: -// -// L=0 => 0 -// L=1 => 100 -// L=2 => 400 -// L=3 => 900 -// L=4 => 1600 -// -// Note: `lines` is a byte (0..255). Even at 255 the delta (255^2*100=6,502,500) -// fits comfortably in uint32 and is then clamped against SCORE_MAX on add. -func (s *Score) Lines(lines byte) { - L := uint32(lines) - s.Add(L * L * 100) -} - -// Add increases the score by num and clamps the result to SCORE_MAX. -// No effect if the score is already at SCORE_MAX -func (s *Score) Add(num uint32) { - *s = min(*s+Score(num), SCORE_MAX) -} diff --git a/game/shape_queue.go b/game/shape_queue.go deleted file mode 100644 index 7d53d8f..0000000 --- a/game/shape_queue.go +++ /dev/null @@ -1,43 +0,0 @@ -package game - -import ( - "math/rand" -) - -type ShapeQueue struct { - shapes []ShapeType -} - -func NewShapeQueue() *ShapeQueue { - sq := &ShapeQueue{ - shapes: []ShapeType{}, - } - sq.Generate() - return sq -} - -func (sq *ShapeQueue) Generate() { - types := []ShapeType{ - SHAPE_I, - SHAPE_J, - SHAPE_L, - SHAPE_O, - SHAPE_S, - SHAPE_T, - SHAPE_Z, - } - rand.Shuffle(len(types), func(i int, j int) { - types[i], types[j] = types[j], types[i] - }) - - sq.shapes = append(sq.shapes, types...) -} - -func (sq *ShapeQueue) Next() Shape { - if len(sq.shapes) < 1 { - sq.Generate() - } - t := sq.shapes[0] - sq.shapes = sq.shapes[1:] - return NewShape(t) -} diff --git a/game/state/handler.go b/game/state/handler.go deleted file mode 100644 index 86fb3e3..0000000 --- a/game/state/handler.go +++ /dev/null @@ -1,8 +0,0 @@ -package state - -type Handler interface { - Enter() - Exit() - Update(transitioner Transitioner, delta float32) - Render() -} diff --git a/game/state/handlers/gameover.go b/game/state/handlers/gameover.go deleted file mode 100644 index c7a5b36..0000000 --- a/game/state/handlers/gameover.go +++ /dev/null @@ -1,59 +0,0 @@ -package handlers - -import ( - "image/color" - - "tetris/assets" - "tetris/engine/audio" - "tetris/engine/core" - "tetris/engine/render" - "tetris/game/state" - - rl "github.com/gen2brain/raylib-go/raylib" -) - -type GameOver struct { - blinkTimer core.IntervalTimer - textColor color.RGBA -} - -func (GameOver *GameOver) Enter() { - audio.Play(assets.SFX_GAME_OVER) - GameOver.blinkTimer = core.NewIntervalTimer(0.2) - GameOver.textColor = rl.White -} - -func (GameOver) Exit() { -} - -func (GameOver) soundDone() bool { - return !audio.IsPlaying(assets.SFX_GAME_OVER) -} - -func (GameOver *GameOver) Update(fsm state.Transitioner, delta float32) { - if GameOver.blinkTimer.UpdateReset(delta) { - if GameOver.textColor == rl.Red { - GameOver.textColor = rl.White - } else { - GameOver.textColor = rl.Red - } - } - - if GameOver.soundDone() { - if rl.IsKeyPressed(rl.KeyEnter) { - fsm.Switch("gameplay") - } else if rl.IsKeyPressed(rl.KeyQ) { - fsm.Switch("menu") - } - } -} - -func (GameOver GameOver) Render() { - render.Begin(rl.Black) - render.DrawTextCenter(340, 200, 32, "Game Over", GameOver.textColor) - if GameOver.soundDone() { - render.DrawTextCenter(340, 290, 16, "Press ENTER to restart", rl.White) - render.DrawTextCenter(340, 316, 16, "Press Q to return to menu", rl.White) - } - render.End() -} diff --git a/game/state/handlers/gameplay.go b/game/state/handlers/gameplay.go deleted file mode 100644 index b65da11..0000000 --- a/game/state/handlers/gameplay.go +++ /dev/null @@ -1,152 +0,0 @@ -package handlers - -import ( - "fmt" - "image/color" - - "tetris/assets" - "tetris/engine/audio" - "tetris/engine/core" - "tetris/engine/render" - "tetris/game" - "tetris/game/draw" - "tetris/game/state" - - rl "github.com/gen2brain/raylib-go/raylib" -) - -type GamePlay struct { - shape game.Shape - shape_pos core.Vec2i8 - score game.Score - dropTimer core.IntervalTimer - moveTimer core.IntervalTimer - lineClearTimer core.IntervalTimer - grid game.Grid - nextShape game.Shape - shapeQueue *game.ShapeQueue - r draw.Renderer - lineClearAnimation game.LineClearAnimation -} - -func NewGamePlay() *GamePlay { - return &GamePlay{ - dropTimer: core.NewIntervalTimer(0.3), - moveTimer: core.NewIntervalTimer(0.1), - lineClearTimer: core.NewIntervalTimer(0.05), - r: draw.Renderer{ - Theme: &draw.Theme{ - FrameBG: color.RGBA{R: 30, G: 30, B: 46, A: 255}, - FrameBorder: color.RGBA{R: 242, G: 205, B: 205, A: 255}, - TextHeader: color.RGBA{R: 242, G: 205, B: 205, A: 255}, - Text: color.RGBA{R: 205, G: 214, B: 244, A: 255}, - GridBackground: color.RGBA{R: 17, G: 17, B: 27, A: 255}, - }, - }, - } -} - -func (gp *GamePlay) Enter() { - gp.grid = game.Grid{} - gp.score = 0 - gp.dropTimer.SetInterval(0.3) - gp.shapeQueue = game.NewShapeQueue() - gp.nextShape = gp.shapeQueue.Next() - gp.SpawnShape() - gp.lineClearAnimation.Reset() - - audio.PlayLooped(assets.SFX_MUSIC) -} - -func (GamePlay) Exit() { - audio.Stop(assets.SFX_MUSIC) -} - -func (gp *GamePlay) SpawnShape() { - gp.shape = gp.nextShape - gp.nextShape = gp.shapeQueue.Next() - gp.shape_pos = core.Vec2i8{X: 4, Y: 0} -} - -func (gp *GamePlay) LockShape() { - audio.Play(assets.SFX_SHAPE_LOCKED) - - for _, block := range gp.shape.Coordinates() { - block = gp.shape_pos.Add(block) - // Check bounds - if block.X < 0 || block.X > int8(gp.grid.Width()) || block.Y < 0 || block.Y > int8(gp.grid.Height()) { - continue - } - gp.grid.Set(byte(block.X), byte(block.Y), gp.shape.GetBlock()) - } -} - -func (gp *GamePlay) Update(fsm state.Transitioner, delta float32) { - if rl.IsKeyPressed(rl.KeyDown) { - gp.dropTimer.SetInterval(0.05) - } else if rl.IsKeyReleased(rl.KeyDown) { - gp.dropTimer.SetInterval(0.3) - } - - if !gp.lineClearAnimation.Completed() { - if gp.lineClearTimer.UpdateReset(delta) { - gp.lineClearAnimation.Update(&gp.grid) - } - return - } - - if rl.IsKeyPressed(rl.KeyUp) { - rotated := gp.shape.RotateCW() - if !game.CheckShapeCollision(gp.shape_pos, &rotated, &gp.grid) { - gp.shape = rotated - } - } - - if gp.moveTimer.UpdateReset(delta) && (rl.IsKeyDown(rl.KeyLeft) || rl.IsKeyDown(rl.KeyRight)) { - new_pos := gp.shape_pos - if rl.IsKeyDown(rl.KeyLeft) { - new_pos.X -= 1 - } else { - new_pos.X += 1 - } - if !game.CheckShapeCollision(new_pos, &gp.shape, &gp.grid) { - gp.shape_pos.X = new_pos.X - } - } - - if gp.dropTimer.UpdateReset(delta) { - new_pos := gp.shape_pos - new_pos.Y += 1 - - // Update position if it does not collide - if game.CheckShapeCollision(new_pos, &gp.shape, &gp.grid) { - gp.LockShape() - gp.SpawnShape() - lines := gp.grid.FullLines() - if len(lines) > 0 { - gp.lineClearAnimation.SetLines(lines) - gp.score.Lines(byte(len(lines))) - audio.Play(assets.SFX_ROW_CLEARED) - } else { - if game.CheckShapeCollision(gp.shape_pos, &gp.shape, &gp.grid) { - fsm.Switch("gameover") - } - } - } else { - gp.shape_pos = new_pos - } - } -} - -func (gp GamePlay) Render() { - render.Begin(gp.r.Theme.GridBackground) - gp.r.DrawGrid(rl.NewVector2(25, 25), gp.grid) - draw.DrawShape(rl.NewVector2(25, 25), gp.shape_pos, gp.shape) - gp.r.DrawFrame(rl.RectangleInt32{X: 400, Y: 25, Width: 250, Height: 100}) - gp.r.DrawHeaderText(410, 30, "Score") - gp.r.DrawText(410, 65, fmt.Sprintf("%.7d", gp.score)) - gp.r.DrawFrame(rl.RectangleInt32{X: 400, Y: 150, Width: 250, Height: 200}) - gp.r.DrawHeaderText(410, 155, "Next") - draw.DrawShape(rl.NewVector2(450, 150), core.NewVec2[int8](1, 3), gp.nextShape) - render.End() -} diff --git a/game/state/handlers/menu.go b/game/state/handlers/menu.go deleted file mode 100644 index c8b51ca..0000000 --- a/game/state/handlers/menu.go +++ /dev/null @@ -1,79 +0,0 @@ -package handlers - -import ( - "tetris/assets" - "tetris/engine/audio" - "tetris/engine/render" - "tetris/game" - "tetris/game/state" - "tetris/game/ui" - "tetris/game/ui/layouts" - "tetris/game/ui/widgets" - "tetris/game/uievents" - - rl "github.com/gen2brain/raylib-go/raylib" -) - -type MainMenu struct { - list *layouts.ListBox -} - -func enterState(fsm state.Transitioner, state string) func() { - return func() { - audio.Play(assets.SFX_MENU_ENTER) - fsm.Switch(state) - } -} - -func NewMainMenu(fsm state.Transitioner) *MainMenu { - return &MainMenu{ - list: layouts.NewListBox([]ui.InputWidget{ - widgets.NewButton("Start", 32, enterState(fsm, "gameplay")), - widgets.NewButton("Options", 32, enterState(fsm, "options")), - widgets.NewButton("Quit", 32, enterState(fsm, "quit")), - }).Spacing(10). - OnSelect(uievents.MenuSelect), - } -} - -func (main *MainMenu) Enter() { - main.list.SetSelected(0) -} - -func (MainMenu) Exit() { -} - -func (menu *MainMenu) Update(fsm state.Transitioner, delta float32) { - menu.list.HandleInput() -} - -func (MainMenu) renderLogo(offset_x, offset_y int32) { - for y := range assets.LOGO_HEIGHT { - for x := range assets.LOGO_STRIDE { - index := assets.Logo[x+(y*assets.LOGO_STRIDE)] - block := game.Block(index) - - if block == game.BLOCK_EMPTY { - continue - } - - src := block.Tile().GetTexRect() - - render.DrawTextureRec(src, rl.Rectangle{ - X: float32(offset_x) + (float32(x) * src.Width * 2), - Y: float32(offset_y) + (float32(y) * src.Height * 2), - Width: src.Width * 2, - Height: src.Height * 2, - }) - } - } -} - -func (menu MainMenu) Render() { - render.Begin(rl.Black) - - menu.renderLogo(20, 150) - menu.list.Draw(340, 400) - - render.End() -} diff --git a/game/state/handlers/options.go b/game/state/handlers/options.go deleted file mode 100644 index 8fabd6d..0000000 --- a/game/state/handlers/options.go +++ /dev/null @@ -1,67 +0,0 @@ -package handlers - -import ( - "tetris/assets" - "tetris/engine/audio" - "tetris/engine/core" - "tetris/engine/render" - "tetris/game" - "tetris/game/state" - "tetris/game/ui" - "tetris/game/ui/layouts" - "tetris/game/ui/widgets" - "tetris/game/uievents" - - rl "github.com/gen2brain/raylib-go/raylib" -) - -type OptionsMenu struct { - sndVolume *widgets.Slider - list *layouts.ListBox -} - -func soundVolumeChanged(widget *widgets.Slider) { - value := core.ByteToClampedFloat32(byte(widget.Value)) - - audio.SetVolume(value) - audio.Play(assets.SFX_MENU_SOUND_VOLUME_SELECT) - - // Store in config and save - game.Config.SoundVolume = byte(widget.Value) - game.Config.Save() -} - -func NewOptionsMenu() *OptionsMenu { - sndVolume := widgets.NewSlider(0, 255, 10).WithOnChange(soundVolumeChanged) - return &OptionsMenu{ - sndVolume: sndVolume, - list: layouts.NewListBox([]ui.InputWidget{ - layouts.NewInputControl(widgets.NewLabel("Sound Volume", 16), sndVolume), - }).Spacing(10).OnSelect(uievents.MenuSelect), - } -} - -func (menu *OptionsMenu) Enter() { - vol := core.ClampedFloat32ToByte(audio.Volume()) - menu.sndVolume.SetValue(int(vol)) -} - -func (OptionsMenu) Exit() { -} - -func (menu *OptionsMenu) Update(fsm state.Transitioner, delta float32) { - if rl.IsKeyPressed(rl.KeyEscape) { - fsm.Switch("menu") - } else { - menu.list.HandleInput() - } -} - -func (opt OptionsMenu) Render() { - render.Begin(rl.Black) - - render.DrawTextCenter(340, 100, 32, "Options", rl.White) - opt.list.Draw(150, 200) - - render.End() -} diff --git a/game/state/machine/machine.go b/game/state/machine/machine.go deleted file mode 100644 index 79e2ff1..0000000 --- a/game/state/machine/machine.go +++ /dev/null @@ -1,72 +0,0 @@ -package machine - -import "tetris/game/state" - -type Machine struct { - current state.Handler - pending string - registry map[string]state.Handler -} - -// New creates an empty machine. -func New() *Machine { - return &Machine{registry: make(map[string]state.Handler)} -} - -// Register binds a name to a factory that returns a fresh state.Handler. -func (m *Machine) Register(name string, handler state.Handler) { - m.registry[name] = handler -} - -// Start switches to the named initial state immediately (calls Enter()). -func (m *Machine) Start(name string) { - m.switchNow(name) -} - -// Switch queues a transition to be applied after the current Update finishes. -func (m *Machine) Switch(name string) { - if name == "" || name == m.pending { - return - } - m.pending = name -} - -// Update ticks the current state, then applies any queued transition. -func (m *Machine) Update(delta float32) bool { - if m.current == nil { - return false - } - m.current.Update(m, delta) - - if m.pending != "" { - if m.pending == "quit" { - return false - } - next := m.pending - m.pending = "" - m.switchNow(next) - } - return true -} - -// Render proxies to current state. -func (m *Machine) Render() { - if m.current != nil { - m.current.Render() - } -} - -func (m *Machine) switchNow(name string) { - handler, ok := m.registry[name] - if !ok { - // Unknown state: dont switch. - return - } - - if m.current != nil { - m.current.Exit() - } - - m.current = handler - m.current.Enter() -} diff --git a/game/state/transitioner.go b/game/state/transitioner.go deleted file mode 100644 index 95af875..0000000 --- a/game/state/transitioner.go +++ /dev/null @@ -1,6 +0,0 @@ -package state - -// Transitioner is the only thing a state needs to know about the machine. -type Transitioner interface { - Switch(name string) // request transition by state name -} diff --git a/game/ui/base/focus.go b/game/ui/base/focus.go deleted file mode 100644 index e0e0b10..0000000 --- a/game/ui/base/focus.go +++ /dev/null @@ -1,26 +0,0 @@ -package base - -// WithFocus is a base component that implements the Focusable interface -type WithFocus struct { - focus bool -} - -// SetFocus - Set the focus value on this component -func (f *WithFocus) SetFocus(value bool) { - f.focus = value -} - -// Focus - focus this component, syntactic sugar for f.SetFocus(true) -func (f *WithFocus) Focus() { - f.SetFocus(true) -} - -// Unfocus - unfocus this component, syntactic sugar for f.SetFocus(false) -func (f *WithFocus) Unfocus() { - f.SetFocus(false) -} - -// HasFocus - Returns true if this component has focus. -func (f WithFocus) HasFocus() bool { - return f.focus -} diff --git a/game/ui/component.go b/game/ui/component.go deleted file mode 100644 index 083c732..0000000 --- a/game/ui/component.go +++ /dev/null @@ -1,14 +0,0 @@ -package ui - -// Focusable is a component that can have focus -type Focusable interface { - SetFocus(value bool) - Focus() - Unfocus() - HasFocus() bool -} - -// ReceivesInput is a component that can receive user input. -type ReceivesInput interface { - HandleInput() -} diff --git a/game/ui/layouts/input_control.go b/game/ui/layouts/input_control.go deleted file mode 100644 index ca256a6..0000000 --- a/game/ui/layouts/input_control.go +++ /dev/null @@ -1,57 +0,0 @@ -package layouts - -import ( - "tetris/engine/core" - "tetris/game/ui" - "tetris/game/ui/base" - "tetris/game/ui/widgets" - - rl "github.com/gen2brain/raylib-go/raylib" -) - -// InputControl is a layout that has a label and an a widget associated with it. -type InputControl struct { - *base.WithFocus - label *widgets.Label - widget ui.InputWidget - spacing int -} - -func NewInputControl(label *widgets.Label, wiget ui.InputWidget) *InputControl { - return &InputControl{ - WithFocus: &base.WithFocus{}, - label: label, - widget: wiget, - spacing: 10, - } -} - -func (ic InputControl) Size() core.Vec2i { - return core.Vec2i{ - X: ic.label.Size().X + ic.widget.Size().X + ic.spacing, - Y: max(ic.label.Size().Y, ic.widget.Size().Y), - } -} - -func (ic *InputControl) SetFocus(value bool) { - ic.WithFocus.SetFocus(value) - ic.widget.SetFocus(value) - - // TODO: use a theme system here so colors are not hardcoded. - if value { - ic.label.SetColor(rl.Red) - } else { - ic.label.SetColor(rl.White) - } -} - -func (ic InputControl) HandleInput() { - ic.widget.HandleInput() -} - -func (ic InputControl) Draw(x, y int32) { - size := ic.Size() - label_y := (int32(size.Y) - int32(ic.label.Size().Y)) / 2 - ic.label.Draw(x, y+label_y) - ic.widget.Draw(x+int32(ic.label.Size().X+ic.spacing), y) -} diff --git a/game/ui/layouts/list_box.go b/game/ui/layouts/list_box.go deleted file mode 100644 index bcbcd08..0000000 --- a/game/ui/layouts/list_box.go +++ /dev/null @@ -1,91 +0,0 @@ -package layouts - -import ( - "tetris/game/ui" - "tetris/game/ui/base" - - rl "github.com/gen2brain/raylib-go/raylib" -) - -type OnSelectCallback func() - -type ListBox struct { - *base.WithFocus - selected int - entries []ui.InputWidget - onSelect OnSelectCallback - spacing int -} - -func NewListBox(entries []ui.InputWidget) *ListBox { - lb := &ListBox{ - WithFocus: &base.WithFocus{}, - entries: entries, - onSelect: func() {}, - } - lb.entries[0].SetFocus(true) - return lb -} - -func (lb *ListBox) Spacing(value int) *ListBox { - lb.spacing = value - return lb -} - -func (lb *ListBox) OnSelect(callback OnSelectCallback) *ListBox { - lb.onSelect = callback - return lb -} - -func (lb ListBox) Entries() []ui.InputWidget { - return lb.entries -} - -func (lb *ListBox) Select(index int) { - if lb.SetSelected(index) { - lb.onSelect() - } -} - -func (lb *ListBox) SetSelected(index int) bool { - if index >= 0 && index < len(lb.entries) { - lb.entries[lb.selected].SetFocus(false) - lb.selected = index - lb.entries[lb.selected].SetFocus(true) - return true - } - return false -} - -func (lb ListBox) Selected() ui.InputWidget { - return lb.entries[lb.selected] -} - -func (lb ListBox) IsSelected(index int) bool { - return lb.selected == index -} - -func (lb *ListBox) Next() { - lb.Select(lb.selected + 1) -} - -func (lb *ListBox) Previous() { - lb.Select(lb.selected - 1) -} - -func (lb *ListBox) HandleInput() { - if rl.IsKeyPressed(rl.KeyDown) { - lb.Next() - } else if rl.IsKeyPressed(rl.KeyUp) { - lb.Previous() - } else { - lb.Selected().HandleInput() - } -} - -func (lb *ListBox) Draw(x, y int32) { - for _, ent := range lb.entries { - ent.Draw(x, y) - y += int32(ent.Size().Y + lb.spacing) - } -} diff --git a/game/ui/widget.go b/game/ui/widget.go deleted file mode 100644 index da9cd17..0000000 --- a/game/ui/widget.go +++ /dev/null @@ -1,18 +0,0 @@ -package ui - -import ( - "tetris/engine/core" -) - -// Widget is a base widget (Can be drawn on screen) -type Widget interface { - Size() core.Vec2i - Draw(x, y int32) -} - -// InputWidget is a widget that also handles user input and can have focus. -type InputWidget interface { - Widget - Focusable - ReceivesInput -} diff --git a/game/ui/widgets/button.go b/game/ui/widgets/button.go deleted file mode 100644 index 62efd5d..0000000 --- a/game/ui/widgets/button.go +++ /dev/null @@ -1,48 +0,0 @@ -package widgets - -import ( - "tetris/engine/core" - "tetris/engine/render" - "tetris/game/ui/base" - - rl "github.com/gen2brain/raylib-go/raylib" -) - -type Button struct { - *base.WithFocus - Text string - TextSize int32 - Action func() -} - -func NewButton(text string, size int32, action func()) Button { - return Button{ - WithFocus: &base.WithFocus{}, - Text: text, - TextSize: size, - Action: action, - } -} - -func (b Button) Size() core.Vec2i { - s := int(b.TextSize) - return core.Vec2i{ - X: len(b.Text) * s, - Y: s, - } -} - -func (b Button) HandleInput() { - if rl.IsKeyPressed(rl.KeyEnter) { - b.Action() - } -} - -func (b Button) Draw(x, y int32) { - // TODO: use a theme system here so colors are not hardcoded. - col := rl.White - if b.HasFocus() { - col = rl.Red - } - render.DrawTextCenter(x, y, b.TextSize, b.Text, col) -} diff --git a/game/ui/widgets/label.go b/game/ui/widgets/label.go deleted file mode 100644 index 027e3d5..0000000 --- a/game/ui/widgets/label.go +++ /dev/null @@ -1,39 +0,0 @@ -package widgets - -import ( - "image/color" - - "tetris/engine/core" - "tetris/engine/render" - - rl "github.com/gen2brain/raylib-go/raylib" -) - -type Label struct { - text string - size int32 - color color.RGBA -} - -func NewLabel(text string, size int32) *Label { - return &Label{ - text: text, - size: size, - color: rl.White, - } -} - -func (label Label) Size() core.Vec2i { - return core.Vec2i{ - X: int(label.size) * len(label.text), - Y: int(label.size), - } -} - -func (label *Label) SetColor(col color.RGBA) { - label.color = col -} - -func (label Label) Draw(x, y int32) { - render.DrawText(x, y, label.size, label.text, label.color) -} diff --git a/game/ui/widgets/slider.go b/game/ui/widgets/slider.go deleted file mode 100644 index 1ae833f..0000000 --- a/game/ui/widgets/slider.go +++ /dev/null @@ -1,136 +0,0 @@ -package widgets - -import ( - "fmt" - "image/color" - - "tetris/engine/core" - "tetris/engine/input" - "tetris/engine/render" - "tetris/game/ui/base" - - rl "github.com/gen2brain/raylib-go/raylib" -) - -const ( - sliderBorderSize = 4 - sliderWidth = 200 - sliderHeight = 30 - sliderTextSize = 12 - sliderTextOffset = 6 -) - -type SliderOnChange func(this *Slider) - -type Slider struct { - *base.WithFocus - Min int - Max int - Step int - Value int - borderSize int32 - onChange SliderOnChange -} - -func NewSlider(min, max, step int) *Slider { - return &Slider{ - WithFocus: &base.WithFocus{}, - Min: min, - Max: max, - Step: step, - borderSize: 4, - } -} - -func (slider *Slider) WithOnChange(callback SliderOnChange) *Slider { - slider.onChange = callback - return slider -} - -func (slider Slider) Size() core.Vec2i { - w := sliderWidth + (sliderBorderSize * 2) - return core.Vec2i{ - X: w + sliderTextOffset + (sliderTextSize * len(slider.ValueText())), - Y: sliderHeight + (sliderBorderSize * 2), - } -} - -func (slider *Slider) HandleInput() { - if input.KeyPressedWithRepeat(rl.KeyLeft) { - slider.Decrement() - } else if input.KeyPressedWithRepeat(rl.KeyRight) { - slider.Increment() - } -} - -func (slider *Slider) SetValue(value int) { - if value != slider.Value { - slider.Value = value - slider.onChange(slider) - } -} - -func (slider *Slider) Increment() { - slider.SetValue(min(slider.Max, slider.Value+slider.Step)) -} - -func (slider *Slider) Decrement() { - slider.SetValue(max(slider.Min, slider.Value-slider.Step)) -} - -func (slider Slider) Percent() float32 { - return float32(slider.Min+slider.Value) / float32(slider.Max) -} - -func (slider Slider) drawTrack(rect rl.RectangleInt32) { - spacing := int32(2) - num_marks := int32(10) - mark_width := float32(rect.Width-((num_marks+1)*spacing)) / float32(num_marks) - - markRect := rl.Rectangle{ - X: float32(rect.X + spacing), - Y: float32(rect.Y + spacing), - Width: mark_width, - Height: float32(rect.Height - (spacing * 2)), - } - - for range num_marks { - rl.DrawRectangleRec(markRect, rl.DarkGray) - markRect.X += mark_width + float32(spacing) - } -} - -func (slider Slider) drawHandle(x, y, width int32, height int32, col color.RGBA) { - handleWidth := int32(10) - x = x + int32(float32(width-handleWidth-2)*slider.Percent()) - rl.DrawRectangle(x, y, handleWidth, height, col) -} - -func (slider Slider) ValueText() string { - return fmt.Sprintf("%d", int(slider.Percent()*100)) -} - -func (slider Slider) Draw(x, y int32) { - // rl.DrawRectangle(x, y, int32(slider.Size().X), int32(slider.Size().Y), rl.Green) - - // TODO: use a theme system here so colors are not hardcoded. - handleColor := rl.White - if slider.HasFocus() { - handleColor = rl.Red - } - - rect := rl.RectangleInt32{ - X: x + sliderBorderSize, - Y: y + sliderBorderSize, - Width: sliderWidth, - Height: sliderHeight, - } - - render.DrawRectOutlineBorder(rect, slider.borderSize, handleColor) - slider.drawTrack(rect) - slider.drawHandle(rect.X+1, rect.Y+1, int32(rect.Width), sliderHeight-2, handleColor) - - textOffset := x + int32(rect.Width) + (sliderBorderSize * 2) + sliderTextOffset - - render.DrawText(textOffset, rect.Y+8, sliderTextSize, slider.ValueText(), rl.White) -} diff --git a/game/uievents/select.go b/game/uievents/select.go deleted file mode 100644 index 7fa6d69..0000000 --- a/game/uievents/select.go +++ /dev/null @@ -1,10 +0,0 @@ -package uievents - -import ( - "tetris/assets" - "tetris/engine/audio" -) - -func MenuSelect() { - audio.Play(assets.SFX_MENU_SELECT) -} diff --git a/main.go b/main.go new file mode 100644 index 0000000..2639219 --- /dev/null +++ b/main.go @@ -0,0 +1,121 @@ +package main + +import ( + "image/color" + + "tetris/assets" + "tetris/engine/core" + "tetris/engine/graphics" + "tetris/engine/render" + "tetris/game" + "tetris/game/draw" + + rl "github.com/gen2brain/raylib-go/raylib" +) + +var ( + shape game.Shape + shape_pos core.Vec2i8 + dropTimer = core.NewIntervalTimer(0.3) + moveTimer = core.NewIntervalTimer(0.1) + grid = game.Grid{} + nextShape game.ShapeType + + r = draw.Renderer{ + Theme: &draw.Theme{ + FrameBG: color.RGBA{R: 30, G: 30, B: 46, A: 255}, + FrameBorder: color.RGBA{R: 242, G: 205, B: 205, A: 255}, + TextHeader: color.RGBA{R: 242, G: 205, B: 205, A: 255}, + Text: color.RGBA{R: 205, G: 214, B: 244, A: 255}, + GridBackground: color.RGBA{R: 17, G: 17, B: 27, A: 255}, + }, + } +) + +func SpawnShape() { + shape = game.NewShape(nextShape) + shape_pos = core.Vec2i8{X: 4, Y: 0} + nextShape = (nextShape + 1) % 7 +} + +func LockShape() { + for _, block := range shape.Coordinates() { + block = shape_pos.Add(block) + // Check bounds + if block.X < 0 || block.X > int8(grid.Width()) || block.Y < 0 || block.Y > int8(grid.Height()) { + continue + } + grid.Set(byte(block.X), byte(block.Y), shape.GetBlock()) + } +} + +func Update(delta float32) { + if rl.IsKeyPressed(rl.KeyUp) { + rotated := shape.RotateCW() + if !game.CheckShapeCollision(shape_pos, &rotated, &grid) { + shape = rotated + } + } + + if moveTimer.UpdateReset(delta) && (rl.IsKeyDown(rl.KeyLeft) || rl.IsKeyDown(rl.KeyRight)) { + new_pos := shape_pos + if rl.IsKeyDown(rl.KeyLeft) { + new_pos.X -= 1 + } else { + new_pos.X += 1 + } + if !game.CheckShapeCollision(new_pos, &shape, &grid) { + shape_pos.X = new_pos.X + } + } + + if dropTimer.UpdateReset(delta) { + new_pos := shape_pos + new_pos.Y += 1 + + // Update position if it does not collide + if game.CheckShapeCollision(new_pos, &shape, &grid) { + LockShape() + SpawnShape() + } else { + shape_pos = new_pos + } + } +} + +func Render() { + render.Begin(r.Theme.GridBackground) + r.DrawGrid(rl.NewVector2(25, 25), grid) + draw.DrawShape(rl.NewVector2(25, 25), shape_pos, shape) + r.DrawFrame(rl.RectangleInt32{X: 400, Y: 25, Width: 250, Height: 100}) + r.DrawHeaderText(410, 30, "Score") + r.DrawText(410, 65, "999999") + r.DrawFrame(rl.RectangleInt32{X: 400, Y: 150, Width: 250, Height: 200}) + render.End() +} + +func main() { + // Set random blocks to test + render.Init(render.Config{ + Title: "Tetris", + WindowWidth: 685, + WindowHeight: 600, + RenderWidth: 685, + RenderHeight: 600, + ScaleFlags: render.SCALE_INTEGER, + }) + defer render.Exit() + + // Load texture + texture := graphics.LoadTextureFromMemory(".png", assets.Sprite) + defer rl.UnloadTexture(texture) + render.SetTexture(texture) + render.SetFont(&assets.Font) + + SpawnShape() + + for !rl.WindowShouldClose() { + Update(rl.GetFrameTime()) + Render() + } +} diff --git a/tetris.go b/tetris.go deleted file mode 100644 index c5dfcbf..0000000 --- a/tetris.go +++ /dev/null @@ -1,71 +0,0 @@ -package main - -import ( - "fmt" - - "tetris/assets" - "tetris/engine/audio" - "tetris/engine/core" - "tetris/engine/graphics" - "tetris/engine/render" - "tetris/game" - "tetris/game/state/handlers" - "tetris/game/state/machine" - - rl "github.com/gen2brain/raylib-go/raylib" -) - -func main() { - if err := game.LoadConfig("./config.json"); err != nil { - fmt.Println("Failed to load config:", err) - } - - render.Init(render.Config{ - Title: "Tetris", - WindowWidth: 685, - WindowHeight: 600, - RenderWidth: 685, - RenderHeight: 600, - ScaleFlags: render.SCALE_INTEGER, - }) - defer render.Exit() - - // Dont close application when user presses escape - rl.SetExitKey(rl.KeyNull) - - // Set window icon - if icon := graphics.LoadImageFromMemory(".png", assets.Icon); icon != nil { - rl.SetWindowIcon(*icon) - rl.UnloadImage(icon) - } - - audio.Init() - defer audio.Exit() - - // Set volume from config - audio.SetVolume(core.ByteToClampedFloat32(game.Config.SoundVolume)) - audio.LoadLibrary(assets.LoadSound()) - - // Load texture - texture := graphics.LoadTextureFromMemory(".png", assets.Sprite) - defer rl.UnloadTexture(texture) - render.SetTexture(texture) - render.SetFont(&assets.Font) - - // Setup state machine. - fsm := machine.New() - fsm.Register("menu", handlers.NewMainMenu(fsm)) - fsm.Register("options", handlers.NewOptionsMenu()) - fsm.Register("gameover", &handlers.GameOver{}) - fsm.Register("gameplay", handlers.NewGamePlay()) - fsm.Start("menu") - - // Enter game loop - for !rl.WindowShouldClose() { - audio.Update() - if !fsm.Update(rl.GetFrameTime()) { - break - } - fsm.Render() - } -}