-
Notifications
You must be signed in to change notification settings - Fork 6
feat: add r/vik000/gnoplace #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package gnoplace | ||
|
|
||
| import ( | ||
| "chain/runtime" | ||
| "time" | ||
|
|
||
| "gno.land/p/nt/avl" | ||
| "gno.land/p/nt/ownable" | ||
| ) | ||
|
|
||
| var ( | ||
| OwnableMain = ownable.NewWithAddress("g13kytw9mpyutwmyg5eq7arqxqcszfl6uq4p89zg") | ||
| OwnableBackup = ownable.NewWithAddress("g1f699cfulem8jvq69pxlm5eq945dzusptzkdrgz") | ||
| ) | ||
|
|
||
| // Resets gnoplace | ||
| func Reset(_ realm) { | ||
| CheckPermission() | ||
|
|
||
| users = avl.NewTree() | ||
| pixels = [300]int{} | ||
| } | ||
|
|
||
| func SetInterval(_ realm, interval int) { | ||
| CheckPermission() | ||
|
|
||
| interval_s = time.Duration(interval) * time.Second | ||
| } | ||
|
|
||
| func CheckPermission() { | ||
| addr := runtime.PreviousRealm().Address() | ||
| if addr == OwnableMain.Owner() || addr == OwnableBackup.Owner() { | ||
| return | ||
| } else { | ||
| panic("access restricted") | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| module = "gno.land/r/vik000/gnoplace" | ||
| gno = "0.9" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| package gnoplace | ||
|
|
||
| import ( | ||
| "chain/runtime" | ||
| "net/url" | ||
| "strconv" | ||
| "time" | ||
|
|
||
| "gno.land/p/moul/md" | ||
| "gno.land/p/nt/avl" | ||
| "gno.land/r/leon/hor" | ||
| ) | ||
|
|
||
| // todo: versionning | ||
|
|
||
| var ( | ||
| users = avl.NewTree() | ||
| pixels = [300]int{0} | ||
| interval_s = 30 * time.Second | ||
| colors = []string{"⬜", "⬛", "🟦", "🟪", "🟧", "🟫", "🟥", "🟨", "🟩"} | ||
| ) | ||
|
|
||
| func init() { | ||
| hor.Register(cross, "GnoPlace", "a mini version of r/place \n🟥🟩🟦🟨") | ||
| } | ||
|
|
||
| // Sets a pixel to a color | ||
| func SetPixel(_ realm, pixel int, color int) { | ||
| // check for errors | ||
| if pixel < 0 || pixel >= len(pixels) { | ||
| panic("invalid pixel") | ||
| } | ||
| if color < 0 || color >= len(colors) { | ||
| panic("invalid color") | ||
| } | ||
|
|
||
| // check if user allowed to set pixel | ||
| lastEvent, ok := users.Get(runtime.PreviousRealm().Address().String()) | ||
|
|
||
| if ok && time.Since(lastEvent.(time.Time)) < interval_s { | ||
| panic("you placed a pixel less than " + interval_s.String() + " ago") | ||
| } | ||
|
|
||
| pixels[pixel] = color | ||
|
|
||
| // record the SetPixel event time for this user | ||
| users.Set(runtime.PreviousRealm().Address().String(), time.Now()) | ||
| } | ||
|
|
||
| // Render renders ui and pixel grid | ||
| func Render(path string) string { | ||
| u, _ := url.Parse(path) | ||
| query := u.Query() | ||
| color := atoiDefault(query.Get("color"), -1) | ||
|
|
||
| // show home | ||
| out := md.H1("GnoPlace") | ||
| out += md.HorizontalRule() | ||
| out += md.H2("1 - Select your pixel color") | ||
|
|
||
| for i, _ := range colors { | ||
| out += md.Link(colors[i], "?color="+strconv.Itoa(i)) + " " | ||
| } | ||
|
|
||
| out += " \n" | ||
|
|
||
| out += "## 2 - Click a pixel to paint it" | ||
| if color >= 0 { | ||
| out += " with color " + colors[color] | ||
| } | ||
| out += " \n" | ||
|
|
||
| // render pixels | ||
| for i, _ := range pixels { | ||
| out += renderPixel(i, color) | ||
| if (i+1)%20 == 0 { | ||
| out += " \n" | ||
| } | ||
| } | ||
|
|
||
| out += md.H2("3 - Wait " + interval_s.String() + " before placing again :)") | ||
| out += md.HorizontalRule() | ||
| out += "If you enjoy gnoplace, please upvote in " | ||
| out += md.Link("the Hall of Realms", "/r/leon/hor:hall?sort=creation") + "!\n" | ||
|
|
||
| return out | ||
| } | ||
|
|
||
| // helper to render a pixel | ||
| func renderPixel(pixel int, color int) string { | ||
| return md.Link(colors[pixels[pixel]], "gnoplace$help&func=SetPixel&.send=&pixel="+strconv.Itoa(pixel)+"&color="+strconv.Itoa(color)) | ||
| } | ||
|
|
||
| // atoiDefault converts string to integer with a default fallback | ||
| func atoiDefault(s string, def int) int { | ||
| if s == "" { | ||
| return def | ||
| } | ||
| i, _ := strconv.Atoi(s) | ||
| return i | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| package gnoplace | ||
|
|
||
| import ( | ||
| "strings" | ||
| "testing" | ||
|
|
||
| "gno.land/p/nt/testutils" | ||
| "gno.land/p/nt/urequire" | ||
| ) | ||
|
|
||
| var ( | ||
| user1 = testutils.TestAddress("user1") | ||
| user2 = testutils.TestAddress("user2") | ||
| user3 = testutils.TestAddress("user3") | ||
| ) | ||
|
|
||
| func TestGnoPlace(t *testing.T) { | ||
| // Test initial render | ||
| output := Render("") | ||
| urequire.True(t, strings.Contains(output, "GnoPlace"), "should contain title") | ||
|
|
||
| // Test render with color parameter | ||
| output = Render("?color=1") | ||
| urequire.True(t, strings.Contains(output, "with color"), "should show selected color") | ||
|
|
||
| // User 1 sets a pixel | ||
| testing.SetRealm(testing.NewUserRealm(user1)) | ||
| urequire.NotPanics(t, func() { | ||
| SetPixel(cross, 0, 1) | ||
| }, "user1 should be able to set pixel") | ||
|
|
||
| // User 1 tries to set another pixel too soon (should panic) | ||
| urequire.AbortsWithMessage(t, "you placed a pixel less than 30s ago", func() { | ||
| SetPixel(cross, 2, 3) | ||
| }, "user1 should not be able to set pixel too soon") | ||
|
|
||
| // User 2 sets a different pixel (should work immediately) | ||
| testing.SetRealm(testing.NewUserRealm(user2)) | ||
| urequire.NotPanics(t, func() { | ||
| SetPixel(cross, 1, 2) | ||
| }, "user2 should be able to set pixel") | ||
| } | ||
|
|
||
| func TestSetPixel_InvalidInputs(t *testing.T) { | ||
| testing.SetRealm(testing.NewUserRealm(user1)) | ||
|
|
||
| // Test invalid pixel (negative) | ||
| urequire.AbortsWithMessage(t, "invalid pixel", func() { | ||
| SetPixel(cross, -1, 1) | ||
| }) | ||
|
|
||
| // Test invalid pixel (out of bounds) | ||
| urequire.AbortsWithMessage(t, "invalid pixel", func() { | ||
| SetPixel(cross, 300, 1) | ||
| }) | ||
|
|
||
| // Test invalid color (negative) | ||
| urequire.AbortsWithMessage(t, "invalid color", func() { | ||
| SetPixel(cross, 0, -1) | ||
| }) | ||
|
|
||
| // Test invalid color (out of bounds) | ||
| urequire.AbortsWithMessage(t, "invalid color", func() { | ||
| SetPixel(cross, 0, 10) | ||
| }) | ||
| } | ||
|
|
||
| func TestAtoiDefault(t *testing.T) { | ||
| urequire.Equal(t, 10, atoiDefault("", 10), "empty string should return default") | ||
| urequire.Equal(t, 5, atoiDefault("5", 10), "valid number should be parsed") | ||
| urequire.Equal(t, 0, atoiDefault("0", 10), "zero should be parsed") | ||
| urequire.Equal(t, 0, atoiDefault("invalid", 10), "invalid string should return 0") | ||
| } | ||
|
|
||
| func TestRenderPixel(t *testing.T) { | ||
| // Set a pixel to test rendering | ||
| testing.SetRealm(testing.NewUserRealm(user3)) | ||
| SetPixel(cross, 0, 1) | ||
|
|
||
| // Test render pixel function | ||
| output := renderPixel(0, 1) | ||
| urequire.True(t, strings.Contains(output, "SetPixel"), "should contain SetPixel call") | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.